2018-07-29 18:52:19 qq_38410730 阅读数 62265

长期以来,Linux一直把具有较好的平均系统响应时间和较高的吞吐量作为调度算法的主要目标。但近年来,鉴于嵌入式系统的要求,Linux2.6在支持系统的实时性方面也做出了重大的改进。

 

Linux进程的时间片与权重参数

在处理器资源有限的系统中,所有进程都以轮流占用处理器的方式交叉运行。为使每个进程都有运行的机会,调度器为每个进程分配了一个占用处理器的时间额度,这个额度叫做进程的“时间片”,其初值就存放在进程控制块的counter域中。进程每占用处理器一次,系统就将这次所占用时间从counter中扣除,因为counter反映了进程时间片的剩余情况,所以叫做剩余时间片。

Linux调度的主要思想为:调度器大致以所有进程时间片的总和为一个调度周期;在每个调度周期内可以发生若干次调度,每次调度时,所有进程都以counter为资本竞争处理器控制权,counter值大者胜出,优先运行;凡是已耗尽时间片(即counter=0)的,则立即退出本周期的竞争;当所有未被阻塞进程的时间片都耗尽,那就不等了。然后,由调度器重新为进程分配时间片,开始下一个调度周期。

Linux基于时间片调度的基本思想如下图所示:

上面的调度方式主要体现的是一种公平性:如果一个进程的剩余时间片多,那么它在前期获得运行的时间片就少,为了公平性,就应该适当的赋予它更高的优先级。但是这仅仅是一个总体的原则,为了应付实际问题中的特殊状况,在上述平等原则的基础上,为了给特殊进程一些特殊照顾,在为进程分配时间片时,Linux允许用户通过一个系统调用nice()来设置参数nice影响进程的优先权。所以,系统真正用来确定进程的优先权时,使用的依据为权重参数weight,weight大的进程优先运行。

weight与nice、counter之间的关系大体如下:

weight 正比于 [counter+(20-nice)]

关于三者之间的这个关系将会在后面的内容中详细的讲到。

因为nice是用户在创建进程时确定的,在进程的运行过程中一般不会改变,所以叫做静态优先级;counter则随着进程时间片的小号在不断减小,是变化的,所以叫做动态优先级。

 

调度策略

目前,标准Linux系统支持非实时(普通)和实时两种进程。与此相对应的,Linux有两种进程调度策略:普通进程调度和实时进程调度。因此,在每个进程的进程控制块中都有一个域policy,用来指明该进程为何种进程,应该使用何种调度策略。

Linux调度的总体思想是:实时进程优先于普通进程,实时进程以进程的紧急程度为优先顺序,并为实时进程赋予固定的优先级;普通进程则以保证所有进程能平均占用处理器时间为原则。所以其具体做法就是:

  • 对于实时进程来说,总的思想是为实时进程赋予远大于普通进程的固定权重参数weight,以确保实时进程的优先级。在此基础上,还分为两种做法:一种与时间片无关,另一种与时间片有关;
  • 对于普通进程来说,原则上以相等的weight作为所有进程的初始权重值,即nice=0,然后在每次进行进程调度时,根据剩余时间片对weight动态调整。

 

普通进程调度策略

如果进程控制块的policy的值为SCHED_OTHER,则该进程为普通进程,适用于普通进程调度策略。

时间片的分配

当一个普通进程被创建时,系统会先为它分配一个默认的时间片(nice=0)。在Linux2.4内核中,进程的默认时间片时按照下面的算法来计算的:

#if HZ < 200
#define TICK_SCALE(x)        ((x)>>2)
#elif HZ < 400
#define TICK_SCALE(x)        ((x)>>1)
#elif HZ < 800
#define TICK_SCALE(x)        (x)
#elif HZ < 1600
#define TICK_SCALE(x)        ((x)<<1)
#dele
#define TICK_SCALE(x)        ((x)<<2)
#endif
#define NICE_TO_TICKS(nice) (TICK_SCALE(20-(nice))+1)
......

在每个调度周期之前,调度器为每个普通进程进程分配时间片的算法为:

p->counter = (p->counter>>1) + NICE_TO_TICKS(p->nice);

其中,p为进程控制块指针。

这里为什么需要加上p->counter>>1呢?这会在本文后面的内容中讲到。

例如,用户定义HZ为100,而counter初值和nice的默认值为0,于是可以计算出counter的值为6 ticks(((20-0)>>2)+1),也就是说,进程时间片的默认值大概为60ms(100Hz,10ms)。

weight的微调函数goodness()

尽管在默认情况下,系统为所有的进程都分配了相等的时间片,但在实际运行时,常常因各种原因使得进程的运行总是有先有后,于是经过一段时间运行后,在各进程之间就会产生事实上的不公平的现象,也就是各个进程在实际使用其时间片的方面形成了差异。剩余时间计数器counter的值就反映了这个差异的程度:该值大的,意味着这个进程占用处理器的时间少(吃亏了);该值小的,意味着这个进程占用处理器的时间多(占便宜了)。

为了尽可能地缩小上述的这个差异,Linux的调度器在调度周期中每次调度时,都遍历就绪列表中的所有进程,并按照各个进程的counter当前值调用函数goodness()对所有进程的weight进行调整,想办法让counter值大的进程的weight大一些,而counter值小的进程的weight小一些。

函数goodness()的主要代码如下:

/*
返回值:
        -1000:从不选择这个
        0:过期进程,重新计算计数值(但它仍旧可能被选中)
        正值:goodness值(越大越好)
        +1000:实时进程,选择这个
*/
 
static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)
{
     int weight;
     weight = -1;

     if (p->policy & SCHED_YIELD) //p->policy表示进程的调度策略,SCHED_YIELD是一种策略,不参与处理器的竞争!
#define SCHED_YIELD          0x10
          goto out;
 
     //非实时进程
     if (p->policy == SCHED_OTHER) {
          weight = p->counter;                //weight的主要成分是counter
          if (!weight)                        //如果时间片用尽
               goto out;
              
#ifdef CONFIG_SMP
          if (p->processor == this_cpu)
               weight += PROC_CHANGE_PENALTY;
#endif
 
          if (p->mm == this_mm || !p->mm)
               weight += 1;
          weight += 20 - p->nice;            //nice越小,权值越大
          goto out;
     }
 
     //实时进程
     weight = 1000 + p->rt_priority;
out:
     return weight;
}

这个函数中就能看出:weight 正比于 [counter+(20-nice)]。

普通进程调度算法中的一些细节

当普通就绪队列中所有非等待进程counter值都减为0时,就在schedule()中对每个进程利用下面的代码:

p->counter = (p->counter>>1) + NICE_TO_TICKS(p->nice);

对所有的进程时间片counter重新赋值。

在重新赋值时,对于耗尽时间片的进程,由于参与计算的counter等于0,因此这个算法就是恢复counter的初值;而对于处于等待状态的进程,由于参与计算的counter大于0,因此这个算法实际上就是把counter的剩余值折半再加上初值。也就是说,因等待而损失了时间片的进程在counter重新被赋值时,系统会适当地给它一些“照顾”,以使其在下一个调度周期中获得更多的时间片,并拥有更高的权重weight。

一个进程的等待次数比较多,通常意味着它的I/O操作比较密集。通过对时间片进行叠加的做法给等待进程赋予更高的优先级,体现了Linux对I/O操作密集型进程的一种体贴。

可见,SCHED_OTHER策略本质上是一种比例共享的调度策略,它的这种设计方法能够保证进程调度时的公平性:一个低优先级的进程在每一个周期中也会得到自己应得的那些运行时间;同时,它提供了nice使用户可以对进程优先级进行干预。

 

实时进程调度策略

凡是进程控制块的policy的值为SCHED_FIFO或SCHED_RR的进程,调度器都将其视为实时进程。

进程控制块中的域rt_pripority就是实时进程的优先级,其范围时1~99。这一属性将在调度时用于对进程权重参数weight的计算。

从上面介绍的函数goodness()代码中可以看到,计算实时进程weight的代码相当简单,即:

weight = 1000 + p->rt_priority;

也就是说,Linux是按照严格的优先级别来选择待运行进程的,并且实时进程的权重weight远高于普通进程。

普通进程参数counter、nice,实时进程参数rt_pripority,调度器调度依据的参数weight之间的关系如下图所示:

Linux允许多个实时进程具有相同的一个优先级别,Linux把所有的实时进程按照其优先级组织成若干个队列,对于同一队列的实时进程可以采用先来先服务和时间片轮转调度两种调度策略。

先来先服务调度(SCHED_FIFO)

该策略是符合POSIX标准的FIFO(先入先出)调度规则。即在同一级别的队列中,先就绪的进程先入队,后就绪的进程后入队。

调度器在选择运行进程时,就以优先级rt_pripority为序查询各个队列,当发现队列中有就绪进程时,就运行处于队列头位置的进程。其后,它就会一直运行,除非出现下述情况之一:

  • 进程被具有更高优先级别进程剥夺了处理器;
  • 进程自己因为请求资源而堵塞;
  • 进程自己主动放弃处理器。

时间片轮转调度(SCHED_RR)

该策略时符合POSIX标准的RR(循环Round-Robin)调度规则。

这种策略与SCHED_FIFI类似,也根据优先级别把就绪进程分成若干个队列,但每个队列被组织成一个循环队列,并且每个进程都拥有一个时间片。调度时,调度器仍然以优先级别为序查询就绪进程对列。当发现就绪进程队列后,也是运行处在队列头部的进程,但是当这个进程将自己的时间片耗完之后,调度器把这个进程放到其队列的队尾,并且再选择处在队头的进程来运行,当然前提条件是这时没有更高优先级别的实时进程就绪。

 

Linux调度时机

进程调度的时机与引起进程调度的原因和进程调度的方式有关。Linux进程调度的时机主要有:

  • 进程状态转换的时刻,如进程中止、进程睡眠等;
  • 可运行队列中新增加一个进程时;
  • 当前进程的时间片用完时;
  • 进程从系统调用返回用户态时;
  • 内核处理完中断后,进程返回用户态时。

但必须注意当前进程控制块中的域need_resched的值为非0时,才允许发生调度。另外,要注意,不能在中断服务程序中调用调度器进行调度。

 

2018-04-18 07:52:06 NeverWA 阅读数 13588

Linux 进程的系统调用

子进程创建

fork() 创建子进程。fork()后会有两个并发进程执行,子进程复制了父进程的数据段,包括全局变量。父进程返回子进程的PID, 子进程返回0。 头文件:stdio.h
vfork() 子进程与父进程共享地址空间。调用vfork()创建的子进程后,父进程被挂起,直到子进程结束。头文件:stdio.h

进程映像的更换

用新的程序代码覆盖原先父进程的代码

execl 系列

将每个命令行参数作为函数的参数传递。参数最后要以NULL作为结束标志。头文件:unistd.h
1. int execl(const char *path, const char *arg, …);
path:路径 arg:选项
2. int execlp(const *file, const char *arg, …);
file:文件名 arg:选项
3. int execle(const char *path, const char *arg, …, char *const envp[]);
path:路径 arg:选项 envp:环境变量

execv 系列

将所有参数包装到一个数组中传递。头文件:unistd.h
1. int execv(const char *path, const char *argv[]);
path:路径 arg:选项
2. int execvp(const *file, const char *argv[]);
file:文件名 arg:选项
3. int execve(const char *path, const char *argv[], char *const envp[]));
path:路径 arg:选项 envp:环境变量

p: 函数的第一个参数是文件程序名
e:需要在envp数组中设置环境变量
若调用成功,无返回值;若调用失败,返回-1。

进程等待

头文件:sys/wait.h

pid_t wait(int *status);
status: 指向子进程的退出码
若不关心子进程的退出状态,则设置status的值为NULL
若执行成功,返回子进程的PID;若执行失败,返回-1
pid_t waitpid(pid_t pid, int *status, int option);
status: 指向子进程的退出码

父进程等待当前进程的子进程终止,在等待期间,父进程阻塞。子进程运行结束后父进程才运行,间接起到进程同步的作用。

进程的终止

终止正在运行的进程。等待父进程调用wait()对齐资源进程回收。

#include<stdlib.h>
void exit(int status);

status:用来传递进程结束时的状态。一般来说,exit(0) 表示程序正常退出,exit⑴/exit(-1)表示程序异常退出。

2017-09-05 14:41:43 Hatsune_Miku_ 阅读数 1109245
进程切换

一开始我并不想写这个笔记,因为太过复杂,我一直想以简单的方式理解内核,只从概念,避免涉及过多的代码。实际上,我写笔记的时候,书已经看到很后面了,因为总要理解更多才能理解之前看似简短实际复杂的内容。但最后发现实际上任何内容都没有办法跳过,即便不想看,也需要了解基本的概念,所以依旧不会拿大段代码,但总会拿少量代码。

如果不感兴趣,我觉得也可以跳过,只需要知道一个概念即可。关于进程切换有更详细的章节。。所以这里也并没有深入更多,只是笔记,也许以后会补充更多内容。

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换(process switch)、任务切换(task switch)或上下文切换(content switch)。

硬件上下文

尽管每个进程都有自己的地址空间,但所有进程必须共享CPU寄存器。因此,在恢复一个进程的执行之前,内核必须确保每个寄存器装载了挂起进程时所需要的值。

进程恢复执行前必须装入寄存器的一组数据成为硬件上下文(hardware context)。硬件上下文是进程可执行上下文的一个自己,因为可执行上下文包含进程执行时所需要的所有信息。在Linux中,进程硬件上下午的一部分存放在TSS段,而剩余部分存放在内核态堆栈中。

在下面描述中,假定用prev局部变量表示切换出的进程描述符,next表示切换进的进程描述符。因此,我们把进程切换定义为这样的行为:保存prev硬件上下文,用next硬件上下文代替prev。因为进程切换经常发生,因此减少保存和装入硬件上下文所话费的时间是非常重要的。

早期Linux版本利用80x86体系结构所需提供的硬件支持,并通过far jmp1指令跳到next进程TSS描述符的选择符来执行进程切换。当执行这条指令时,CPU通过自动保存原来的硬件上下文,装入新的硬件上下文来执行硬件上下文切换。但Linux2.6使用软件执行进程切换,原因有:

  1. 通过一组mov指令逐步执行切换,这样能较好地控制所装入的数据的合法性,一面被恶意用户伪造。far jmp指令不会有这样的检查。
  2. 旧方法和新方法所需时间大致相同。

进程切换值发生在内核态,在执行进程切换之前,用户态进程使用的所有寄存器内容已保存在内核堆栈上,这也包括ss和esp这对寄存器的内容。

任务状态段

80x86体系结构包含了一个特殊的段类型,叫任务状态段(Task State Segment,TSS)来存放硬件上下文,尽管Linux并不使用硬件上下文切换,但是强制它为系统中每个不同的CPU创建一个TSS,这样做主要有两个理由:

  1. 当80x86的一个CPU从用户态切换到内核态时,它就从TSS中后去内核态堆栈的地址。
  2. 当用户态进程试图通过in或out指令访问一个I/O端口时,CPU需要访问存放在TSS中的I/O许可位图以检查该进程是否有访问端口的权利。

更确切的说,当进程在用户态执行in或out指令时,控制单元执行下列操作:

  1. 检查eflags寄存器中的2位IOPL字段,如果字段的值为3,控制单元就执行I/O指令。否则,执行下一个检查。
  2. 访问tr寄存器以确定当前的TSS和相应的I/O许可权位图。
  3. 检查I/O指令中指定的I/O端口在I/O许可权位图中对应的位,如果该位清,这条指令就执行,否则控制单元产生一个异常。

tss_struct结构描述TSS的格式,init_tss数组为系统上每个不同的CPU存放一个TSS。在每次进程切换时,内核都更新TSS的某些字段以便相应的CPU控制单元可以安全地检索到它需要的信息。因此,TSS反映了CPU上当前进程的特权级,但不必为没有在运行的进程保留TSS。

每个TSS有它自己8字节的任务状态段描述符(Task State Segment Descriptor,TSSD)。这个描述符包括指向TSS起始地址的32位Base字段,20位Limit字段。TSSD的S标志位被清0,以表示相应的TSS时系统段的事实。

Type字段被置位11或9以表示这个段实际上是一个TSS。在Intel的原始设计中,系统中的每个进程都应当指向自己的TSS;Type字段的第二个有效位叫Busy位;如果进程正由CPU执行,则该位置1,否则为0。在Linux的设计中,每个CPU只有一个TSS,因此Busy位总是为1.

由Linux创建的TSSD存放在全局描述符表(GDT)中,GDT的基地址存放在每个CPU的gdtr寄存器中。每个CPU的tr寄存器包含相应TSS的TSSD选择符,也包含了两个隐藏的非编程字段:TSSD的Base字段和Limit字段。这样,处理器就能够直接TSS寻址而不需要从GDT中检索TSS地址。

thread字段

在每次进程切换时,被替换的进程的硬件上下文必须保存在别处。不能像Intel原始设计那样保存在TSS中,因为Linux为每个处理器而不是为每个进程使用TSS。

因此,每个进程描述符包含一个类型为thread_structthread字段,只要进程被切换出去,内核就把其硬件上下文保存在这个结构中。随后可以看到,这个数据结构包含的字段涉及大部分CPU寄存器,但不包括eax、ebx等等这些通用寄存器。它们的值保留在内核堆栈中。

执行进程切换

进程切换可能只发生在精心定义的点:schedule()函数,这个函数很长,会在以后更长的篇幅里讲解。。这里,只关注内核如何执行一个进程切换。

进程切换由两步组成:

  1. 切换页全局目录以安装一个新的地址空间。
  2. 切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含CPU寄存器。

switch_to宏

进程切换的第二步由switch_to宏执行。它是内核中与硬件关系最为密切的例程之一,必须下很多功夫了解。

<include/asm-generic/system.h>

/* context switching is now performed out-of-line in switch_to.S */
extern struct task_struct *__switch_to(struct task_struct *,
        struct task_struct *);
#define switch_to(prev, next, last)\
    do {\
        ((last) = __switch_to((prev), (next)));\
    } while (0)

首先,该宏有三个参数,prevnextlastprevnext的作用仅是局部变量prevnext的占位符,即它们是输入参数,分别表示被替换进程和新进程描述符的地址在内存中的位置。

在任何进程切换中,涉及到的是三个进程而不是两个。假设内核决定暂停进程A而激活进程B,在schedule()函数中,prev指向A的描述符,而next指向B的进程描述符。switch_to宏一旦使A暂停,A的执行流就被冻结。

随后,当内核想再次激活A,就必须暂停另一个进程C,因为这通常不是B,因为B有可能被其他进程比如C切换。于是就要用prev指向C而next指向A来执行另一个switch_to宏。当A恢复它执行的流时,就会找到它原来的内核栈,于是prev局部变量还是指向A的描述符而next指向B的描述符。此时,代表进程A执行的内核就失去了对C的任何引用。但引用对于完成进程切换是有用的,所以需要保留。

switch_to宏的最后一个参数是输出参数,它表示宏把进程C的描述符地址写在内存的什么位置了,不过,这个是在恢复A执行之后完成的。在进程切换之前,宏把第一个输入参数prev表示的变量存入CPU的eax寄存器。在完成进程切换,A已经恢复执行时,宏把CPU的eax寄存器的内容写入由第三个参数last所指示的A在内存中的位置。因为CPU寄存器不会在切换点发生变化,所以C的描述符地址也存在内存的这个位置。在schedule()执行过程中,last参数指向A的局部变量prev,所以prev被C的地址覆盖。

__switch_to()函数

__switch_to()函数执行大多数开始于switch_to()宏的进程切换。这个函数作用于prev_pnext_p参数,这两个参数表示前一个进程和新进程。这个函数的调用不同于一般的函数调用。因为__switch_to()从eax和edx取参数prev_pnext_p,而不像大多数函数一样从栈中取参数。

<arch/x86/kernel/process_32.c>

__switch_to(
    struct task_struct *prev_p,
    struct task_struct *next_p)
{
    struct thread_struct *prev = &prev_p->thread,
                 *next = &next_p->thread;
    int cpu = smp_processor_id();
    struct tss_struct *tss = &per_cpu(init_tss, cpu);
    bool preload_fpu;

    preload_fpu = tsk_used_math(next_p) && next_p->fpu_counter > 5;

    __unlazy_fpu(prev_p);

    if (preload_fpu)
        prefetch(next->xstate);

    load_sp0(tss, next);

    lazy_save_gs(prev->gs);

    load_TLS(next, cpu);

    if (get_kernel_rpl() && unlikely(prev->iopl != next->iopl))
        set_iopl_mask(next->iopl);

    if (unlikely(task_thread_info(prev_p)->flags 
        & _TIF_WORK_CTXSW_PREV
        || task_thread_info(next_p)->flags
        & _TIF_WORK_CTXSW_NEXT))
        __switch_to_xtra(prev_p, next_p, tss);

    if (preload_fpu)
        clts();

    arch_end_context_switch(next_p);

    if (preload_fpu)
        __math_state_restore();
    if (prev->gs | next->gs)
        lazy_load_gs(next->gs);

    percpu_write(current_task, next_p);

    return prev_p;
}

这个函数执行步骤如下:

执行由__unlay_fpu()宏代码产生的代码,以有选择地保存prev_p进程的FPU、MMX以及XMM寄存器的内容。

执行smp_processor_id()宏获得本地CPU的下表,即执行代码的CPU。该宏从当前进程的thread_info结构的cpu字段获得下标并保存到cpu局部变量。

next_p->thread.esp0装入对应于本地CPU的TSS的esp0字段。其实,任何由sysenter汇编指令产生的从用户态到内核态的特权级转换将把这个地址拷贝到esp寄存器中。

next_p进程使用的线程局部存储(TLS)段装载入本地CPU的全局描述符表。

fsgs段寄存器的内容分别存放在prev_p->thread.fsprev_p->thread.gs中。esi寄存器指向prev_p->thread结构。

如果fsgs段寄存器已经被prev_pnext_p进程中的任意一个使用,则将next_p进程的thread_struct描述符中保存的值装入这些寄存器。

next_p->thread.debugreg数组内容装载dr0…dr7中的6个调试寄存器。只有在next_p被挂起时正在使用调试寄存器,这种操作才能进行。

如果必要,则更新TSS中的I/O位图。然后终止,prev_p参数被拷贝到eax,因为缺省情况下任何C函数的返回值被传给eax寄存器。所以eax的值在调用__switch_to()的过程中被保护起来;这很重要,因为调用该函数时会假定eax总是用来存放将被替换的进程描述符地址。

汇编语言指令ret把栈定保存的返回地址装入eip程序计数器。不过,__swtich_to()函数时通过简单的跳转被调用的。因此,ret汇编指令在栈中找到标号为1的指令地址,其中标号为1的地址是由switch_to()宏推入堆栈的。

  1. far jmp指令既修改cs寄存器,也修改eip寄存器,而简单的jmp之类值修改eip寄存器。 
    文章原帖地址:http://guojing.me/linux-kernel-architecture/posts/process-switch/

2018-07-27 19:21:29 qq_38410730 阅读数 16861

在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程叫做根进程。根进程是Linux中所有进程的祖宗,其余进程都是根进程的子孙。具有同一个父进程的进程叫做兄弟进程。

Linux进程创建的过程示意图如下所示:

 

子进程的创建

在Linux中,父进程以分裂的方式来创建子进程,创建一个子进程的系统调用叫做fork()。

系统调用fork()

为了在一个进程中分裂出子进程,Linux提供了一个系统调用fork()。这里所说的分裂,实际上是一种复制。因为在系统中表示一个进程的实体是进程控制块,创建新进程的主要工作就是要创建一个新控制块,而创建一个新控制块最简单的方法就是复制。

当然,这里的复制并不是完全复制,因为父进程控制块中某些项的内容必须按照子进程的特性来修改,例如进程的标识、状态等。另外,子进程控制块还必须要有表示自己父进程的域和私有空间,例如数据空间、用户堆栈等。下面的两张图就表示父进程和相对应的子进程的内存映射:

稍微介绍一下进程地址空间:

进程的数据区也就是未初始化的数据(.bss)、已初始化的数据(.data);进程的栈区也就是进程的用户栈和堆;进程程序代码就是进程的程序文件(.text);除此之外还有进程的系统堆栈区。可见下面的这张更详细的介绍图:

例子:

#include <stdio.h>

int count1=0;
int main(void)
{
    int pid;
    int count2=0;
    count1++;
    count2++;
    printf("count1=%d,count2=%d\n",count1,count2);

    pid=fork();
    count1++;
    count2++;
    printf("count1=%d,count2=%d\n",count1,count2);
    printf("pid=%d\n"pid);

    return 0;
}

程序运行结果为:

由此可知:

  • 函数fork()却是分裂出了两个进程:因为自函数fork()之后执行了两遍之后的代码(先子进程一次,后父进程一次)。同时,这也证明了父进程和子进程运行的是同一个程序,也正是这个理由,系统并未在内存中给子进程配置独立的程序运行空间,而只是简单地将程序指针指向父进程的代码;
  • 两个进程具有各自的数据区和用户堆栈,在函数fork()生成子进程时,将父进程数据区和用户堆栈的内容分别复制给了子进程。同时,接下来的内容,父进程和子进程都是对自己的数据区和堆栈中的内容进行修改运算了。

其实,在父进程中调用fork()之后会产生两种结果:一种为分裂子进程失败,另一种就是分裂子进程成功。如果fork()失败,则返回-1,;否则会出现父进程和子进程两个进程,在子进程中fork()返回0,在父进程中fork()返回子进程的ID。系统调用fork()工作流程示意图如下:

也就是说,在fork()函数之前需要确认内核中有足够的资源来完成。如果资源满足要求,则内核slab分配器在相应的缓冲区中构造子进程的进程控制块,并将父进程控制块中的全部成员都复制到子进程的控制块,然后再把子进程控制块必须的私有数据改成子进程的数据。当fork()返回到用户空间之前,向子进程的内核栈中压入返回值0,而向父进程内核堆栈压入子进程的pid。最后进行一次进程调度,决定是运行子进程还是父进程。

显然,为了能够使子程序和父程序执行不同的代码,在fork()之后应该根据fork()的返回值使用分支结构来组成程序代码。例如:

#include <stdio.h>
#include <sys/types.h>

int main(void)
{
    pid_t pid;

    pid = fork();
    if(pid < 0){
        ...                //打印fork()失败信息
    }else if(pid == 0){
        ...                //子进程代码
    }else{
        ...                //父进程代码
    }

    return 0;
}

在代码中获得当前进程pid的函数为:getpid();

在代码中获得当前进程父进程pid的函数为:getppid()。

这里需要注明一点:父子进程的调度的顺序是由调度器决定的,与进程的创建顺序无关。

关于子进程的时间片

与UCOSIII不同,Linux在大多数情况下是按时间片来进行进程调度的。当某一个进程所拥有的时间片用完之后,内核会立即剥夺它的运行权,停止它的执行。那么当子进程被创建出来之后,这个子进程的初始时间片应该是多大呢?

Linux规定,如果父进程只是如之前那样简单地创建了一个子进程,那么系统会将父进程剩余的时间片分成两份,一份留给父进程,另一份作为子进程的时间片。因为之前的这种简单方式下,父子进程使用的是同一个代码,还没有成为两个真正的各自独立的进程,所以没有资格享用两份时间片。

 

与进程相关的系统调用

函数execv()

为了在程序运行中能够加载并运行一个可执行文件,Linux提供了系统调用execv()。其原型为:

int execv(const char* path, char* const argv[]);

其中,参数path为可执行文件路径,argv[]为命令行参数。

如果一个进程调用了execv(),那么该函数便会把函数参数path所指定的可执行文件加载到进程的用户内存空间,并覆盖掉原文件,然后便运行这个新加载的可执行文件。

在实际应用中,通常调用execv()的都是子进程。人们之所以创建一个子进程,其目的就是执行一个与父进程代码不同的程序,而系统调用execv()就是子进程执行一个新程序的手段之一。子进程调用execv()之后,系统会立即为子进程加载可执行文件分配私有程序内存空间,从此子进程也成为一个真正的进程。

如果说子进程是父进程的“儿子”,那么子进程在调用execv()之前,它所具有的单独用户堆栈和数据区也仅相当于它的私有“房间”;但因它还没有自己的“住房”,因此也只能寄住在“父亲”家,而不能“自立门户”,尽管它有自己的“户口”(进程控制块)。

调用execv()后,父进程与子进程存储结构的示意图如下:

与上文刚fork()的内存映像图相比,刚调用fork()之后,父子共同使用同一块程序代码;而调用execv()之后,子程序拥有了自己的程序代码区。

例子:

#include <stdio.h>
#include <sys/types.h>

int main(void)
{
    pid_t pid;

    if(!(pid=fork())){
        execv("./hello.o",NULL);
    }else {
        printf("my pif is %d\n", getpid());
    }

    return 0;
}

而可执行文件./hello.o可以编写一个.c程序再进行编译获得。

函数execv()其实是Linux的exec函数族成员之一,该函数族一共有5个函数和1个系统调用,分别是:

int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., char* const envp[]);
int execv(const char* path, const char* argv[]);
int execvp(const char* file, const char* argv[]);
int execve(const char* path, const char* argv[], char* const envp[]);

其中,只有execv()是真正意义上的系统调用,其他的都是在此基础上经过包装的库函数。

execv函数族的作用是,根据指定的文件名找到可执行文件,并将其关联到调用exec族函数的进程,从而使进程执行该可执行文件。简单地说,就是用execv族函数加载的程序文件代替该进程原来的程序文件。

与一般的函数不同,exec族函数执行成功后一般不会返回调用点,因为它运行了一个新的程序,进程的代码段、数据段和堆栈等都已经被新的数据所取代,只留下进程ID等一些表面信息仍保持原样,虽然还是旧的躯壳,但是其实质内容已经全部变化了。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。

系统调用wait()

虽然子进程调用函数execv()之后拥有自己的内存空间,称为一个真正的进程,但由于子进程毕竟由父进程所创建,所以按照计算机技术中谁创建谁负责销毁的惯例,父进程需要在子进程结束之后释放子进程所占用的系统资源。

为实现上述目标,当子进程运行结束后,系统会向该子进程的父进程发出一个信息,请求父进程释放子进程所占用的系统资源。但是,父进程并没有准确的把握一定结束于子进程结束之后,那么为了保证完成为子进程释放资源的任务,父进程应该调用系统调用wait()。

如果一个进程调用了系统调用wait(),那么进程就立即进入等待状态(也叫阻塞状态),一直等到系统为本进程发送一个消息。在处理父进程与子进程的关系上,那就是在等待某个子进程已经退出的信息;如果父进程得到了这个信息,父进程就会在处理子进程的“后事”之后才会继续运行。

也就是说,wait()函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

如果父进程先于子进程结束进程,则子进程会因为失去父进程而成为“孤儿进程”。在Linux中,如果一个进程变成了“孤儿进程”,那么这个进程将以系统在初始化时创建的init进程为父进程。也就是说,Linux中的所有“孤儿进程”以init进程为“养父”,init进程负责将“孤儿进程”结束后的资源释放任务。

这里区分一下僵尸进程和孤儿进程:

  • 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作;
  • 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程(也就是进程为中止状态,僵死状态)。

Linix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

参考文章:孤儿进程与僵尸进程[总结]

系统调用exit()

系统调用exit()用来终结一个进程,通常这个系统调用会在一些与进程控制有关的系统调用中被调用。

如果一个进程调用exit(),那么这个进程会立即退出运行,并负责释放被中止进程除了进程控制块之外的各种内核数据结构。这种只剩下“身份证”的进程叫做“僵尸进程”,其进程控制块域state的值为TASK_ZOMBLE。

一个因调用exec函数族函数而执行新的可执行文件的子进程,当进程执行结束时,便会执行系统调用exit()而使自己成为一个“僵尸进程”,这也正是父进程要负责销毁子进程的进程控制块的原因。

另外,exit()执行到最后,将调用进程调度函数schedule()进行一次进程调度。

这里区分一下exit()和_exit()函数:

exit()定义在stdlib.h文件,_exit()定义在unistd.h文件中。同时两者的步骤也是不一样的:

具体体现在哪里呢?

在Linux标准库中,有一套称为高级I/O函数,例如我们所熟知的printf、fopen、fread、fwrite都在此列,它们也被称为缓冲I/O。其特征是对应每一个打开的文件,都存在一个缓冲区,在每次读文件时会多读若干条记录,这样下次读文件时就可以直接从内存的缓冲区去读。在每次写文件时也会先写入缓冲区,当缓冲区写满,或者我们fflush()手动的刷新缓冲区,或者遇到\n、EOF这样的结束符,才会把对应的数据从缓冲区写入到文件中。这样的好处是大大增加的文件的读写的速度,因为我们都知道磁盘上的读写速度是很慢的,但这也为我们编程带来了一点麻烦,例如有些数据,我们认为已经写入了文件,但实际上它们很可能存在在缓冲区中。

也就是说,_exit()函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构;exit()函数则在这些基础上做了一些小动作,在执行退出之前还加了若干道工序。exit()函数与_exit()函数的最大区别在于exit()函数在调用exit  系统调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件。也就是图中的“清理I/O缓冲”。

参考文章:exit函数和_exit函数的区别

系统调用vfork()

vfork()是Linux提供的另一个用来生成一个子进程的系统调用。

与fork()不同,vfork()并不把父进程全部复制到子进程中,而只是用用赋值指针的方法使子进程与父进程的资源实现共享。由于函数vfork()生成的子进程与父进程共享同一块内存空间,因此实质上vfork()创建的是一个线程,但习惯上人们还是叫它子进程。

用vfork()创建子进程且调用execve()之后,子进程的内存映像如下所示:

这里区分一下fork()与vfork():

  • fork():子进程拷贝父进程的数据段,代码段 。vfork():子进程与父进程共享数据段 ;
  • fork():父子进程的执行次序不确定 。vfork():保证子进程先运行,在调用execve()或exit()之前,与父进程数据是共享的,在它调用execve()或exit()之后,父进程才可能被调度运行。

注意:由于vfork()保证子进程先运行,在它调用execve()或exit()之后,父进程才可能被调度运行。如果在调用这两个函数之前,子进程依赖于父进程的进一步动作,则会导致死锁。 

 

内核中的进程和线程

操作系统是一个先于其他程序运行的程序,那么当系统中除了操作系统之外没有其他的进程时怎么办?同时,系统中的所有用户进程必须由父进程来创建,那么“祖宗进程”是什么呢?

前一个问题由Linux的进程0来解决,后一个问题由Linux的进程1来解决。其实,前者是一个内核进程,而后者先是一个内核进程,后来又变为用户进程。

内核线程及其创建

Linux实现线程的机制非常独特,它没有另外定义线程的数据结构和调度算法,只不过在创建时不为其分配单独的内存空间。如果线程运行在用户空间,那么它就是用户线程;如果运行在内核空间,那么它就是内核线程。并且,无论是线程还是进程,Linux都用同一个调度器对它们进行调度。

但是Linux却单独提供了一个用于创建内核线程的函数kernel_thread()。该函数的原型如下:

int kernel_thread(int (* fn)(void *), void* arg, unsigned long flags);

其中,fn指向线程代码;arg为线程代码所需的入口参数。

内核线程周期性执行,通常用来完成一些需要对内核幕后完成的任务,例如磁盘高速缓存的刷新、网络连接的维护和页面的交换等,所以它们也叫做内核任务。

进程0

计算机启动之后,首先进行Linux的引导和加载,在Linux内核加载之后,初始化函数start_kernel()会立即创建一个内核线程。因为Linux对线程和进程没有太严格的区分,所以就把这个由初始化函数创建的线程叫做进程0。

进程0的执行代码时内核函数cpu_idel(),该函数中只有一条hlt(暂停)指令;也就是说,这个进程什么工作也没做,所以也叫做空闲进程。该空闲进程的PCB叫做init_task(),当系统没有可运行的其它进程时,调度器才会选择进程0来运行。

进程1

进程1也叫做init进程,它是内核初始化时创建的第2个内核线程,其运行代码为内核函数init()。

该函数首先创建kswapd()等4个与内存管理有关的内核线程。接下来,在内核的init()中调用execve()系统调用装入另一个需要在用户空间运行的init函数代码,于是进程1就变成一个普通的进程。它是用户空间的第一个进程,所以它就成了其他用户进程的根进程。

只要系统,init进程就永不中止,它负责创建和监控操作系统外层所有进程的活动。

守护进程

守护进程是不受终端控制并在后台运行的进程。Linux使用了很多守护进程,在后台做一些经常性的注入定期进行页交换之类的例行工作。

守护进程一般可以通过以下方式启动:

  • 在系统启动时由启动脚本启动,这些启动脚本通常放在/etc/rc.d目录下;
  • 利用inetd超级服务器启动,大部分网络服务都是这样启动的,如ftp、telnet等;
  • 由cron定时启动以及在终端用nohup启动的进程也是守护进程。

 

2012-02-23 11:36:19 wojiaopanpan 阅读数 564338
关键字: linux 查进程、杀进程、起进程
1.查进程
    ps命令查找与进程相关的PID号:
    ps a 显示现行终端机下的所有程序,包括其他用户的程序。
    ps -A 显示所有程序。
    ps c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。
    ps -e 此参数的效果和指定"A"参数相同。
    ps e 列出程序时,显示每个程序所使用的环境变量。
    ps f 用ASCII字符显示树状结构,表达程序间的相互关系。
    ps -H 显示树状结构,表示程序间的相互关系。
    ps -N 显示所有的程序,除了执行ps指令终端机下的程序之外。
    ps s 采用程序信号的格式显示程序状况。
    ps S 列出程序时,包括已中断的子程序资料。
    ps -t<终端机编号> 指定终端机编号,并列出属于该终端机的程序的状况。
    ps u 以用户为主的格式来显示程序状况。
    ps x 显示所有程序,不以终端机来区分。
  
    最常用的方法是ps aux,然后再通过管道使用grep命令过滤查找特定的进程,然后再对特定的进程进行操作。
    ps aux | grep program_filter_word,ps -ef |grep tomcat

ps -ef|grep java|grep -v grep 显示出所有的java进程,去处掉当前的grep进程。
  
2.杀进程
   使用kill命令结束进程:kill xxx
   常用:kill -9 324
   Linux下还提供了一个killall命令,可以直接使用进程的名字而不是进程标识号,例如:# killall -9 NAME

3.进入到进程的执行文件所在的路径下,执行文件 ./文件名

附:

这是本人花了两天时间整理得来的,一些最常用的地球人都知道的命令就省去啦!最后提供pdf手册下载

1. 更改档案拥有者
命令 : chown [-cfhvR] [--help] [--version] user[:group] file...
功能 : 更改文件或者文件夹的拥有者
参数格式 :
      user : 新的档案拥有者的使用者 IDgroup : 新的档案拥有者的使用者群体(group)
         -c : 若该档案拥有者确实已经更改,才显示其更改动作
         -f : 若该档案拥有者无法被更改也不要显示错误讯息
         -h : 只对于连结(link)进行变更,而非该 link 真正指向的档案
         -v : 显示拥有者变更的详细资料
         -R : 对目前目录下的所有档案与子目录进行相同的拥有者变更(即以递回的方式逐个变更)

例如:chown -R oracle:oinstall /oracle/u01/app/oracle 
      更改目录拥有者为oracle

2. 修改权限
    命令:chmod (change mode)
    功能:改变文件的读写和执行权限。有符号法和八进制数字法。
    选项:(1)符号法:
  命令格式:chmod {u|g|o|a}{+|-|=}{r|w|x} filename
          u (user)   表示用户本人。
          g (group)  表示同组用户。
          o (oher)   表示其他用户。
          a (all)    表示所有用户。
          +          用于给予指定用户的许可权限。
          -          用于取消指定用户的许可权限。
          =          将所许可的权限赋给文件。
          r (read)   读许可,表示可以拷贝该文件或目录的内容。
          w (write)  写许可,表示可以修改该文件或目录的内容。
          x (execute)执行许可,表示可以执行该文件或进入目录。
 
          (2)八进制数字法:  
  命令格式:chmod abc file
  其中a,b,c各为一个八进制数字,分别表示User、Group、及Other的权限。
          4 (100)    表示可读。
          2 (010)    表示可写。
          1 (001)    表示可执行。
  若要rwx属性则4+2+1=7;
  若要rw-属性则4+2=6;
  若要r-x属性则4+1=5。

    例如:# chmod a+rx filename
            让所有用户可以读和执行文件filename。
          # chmod go-rx filename
            取消同组和其他用户的读和执行文件filename的权限。
          # chmod 741 filename
            让本人可读写执行、同组用户可读、其他用户可执行文件filename。
  # chmod -R 755 /home/oracle
    递归更改目录权限,本人可读写执行、同组用户可读可执行、其他用户可读可执行

3. 修改文件日期
    命令:touch
    格式:touch filenae
    功能:改变文件的日期,不对文件的内容做改动,若文件不存在则建立新文件。
    例如:% touch file

4. 链接文件
    命令:ln (link)
    格式:ln [option] filename linkname
          ln [option] directory pathname
    功能:为文件或目录建立一个链。其中,filename和directory是源文件名和
          源目录名;linkname和pathname分别表示与源文件或源目录名相链接的
          文件或目录。
    选项:-s  为文件或目录建立符号链接。不加-s表示为文件或目录建立硬链接
    注释:链接的目地在于,对一个文件或目录赋予两个以上的名字,使其可以出
          现在不同的目录中,既可以使文件或目录共享,又可以节省磁盘空间。
    例如:% ln -s filename linkname

5. 显示日期
    命令:date
    例如:% date

6. 显示日历
    命令:cal (calendar)
    格式:cal [month] year
    功能:显示某年内指定的日历
    例如:% cal 1998 

7. 显示文件头部
    命令:head
    格式:head [option] filename
    功能:显示文件的头部
    选项:缺省  显示文件的头10行。
          -i    显示文件的开始 i行。
    例如:% head filename

8. 显示文件尾部
    命令:tail
    格式:tail [option] filename
    功能:显示文件的尾部
    选项:缺省  显示文件的末10行。
          -i    显示文件最后 i行。
          +i    从文件的第i行开始显示。
    例如:% tail filename

9. 显示用户标识
    命令:id
    格式:id [option] [user]
    功能:显示用户标识及用户所属的所有组。
    选项:-a 显示用户名、用户标识及用户所属的所有组
    注释:
    例如:% id username

10. 查看当前登录的用户
    命令:users

11. 显示都谁登录到机器上
    命令:who
    格式:who
    功能:显示当前正在系统中的所有用户名字,使用终端设备号,注册时间。
    例如:% who

12. 显示当前终端上的用户名
    命令:whoami
    格式:whoami
    功能:显示出当前终端上使用的用户。
    例如:% whoami

13. 寻找文件
    命令:find
    格式:find pathname [option] expression
    功能:在所给的路经名下寻找符合表达式相匹配的文件。
    选项:-name     表示文件名
          -user     用户名,选取该用户所属的文件
          -size     按大小查找,以block为单位,一个block是512B
          -mtime n  按最后一次修改时间查找,选取n天内被修改的文件
  -perm     按权限查找
          -type     按文件类型查找
  -atime    按最后一次访问时间查找

    例如:% find ./ -name '*abc*' -print

14. 搜索文件中匹配符
    命令:grep
    格式:grep [option] pattern filenames
    功能:逐行搜索所指定的文件或标准输入,并显示匹配模式的每一行。
    选项:-i    匹配时忽略大小写
  -v 找出模式失配的行

    例如:% grep -i 'java*' ./test/run.sh

15. 统计文件字数
    命令:wc [option] filename
    功能:统计文件中的文件行数、字数和字符数。
    选项:-l 统计文件的行数
-w 统计文件的单词数
-c 统计文件的字符数
    注释:若缺省文件名则指标准输入
    例如:% wc -c ./test/run.sh

16. 显示磁盘空间
    命令:df (disk free)
    格式:df [option]
    功能:显示磁盘空间的使用情况,包括文件系统安装的目录名、块设备名、总
          字节数、已用字节数、剩余字节数占用百分比。
    选项:
-a:显示全部的档案系统和各分割区的磁盘使用情形
-i:显示i -nodes的使用量
-k:大小用k来表示 (默认值)
-t:显示某一个档案系统的所有分割区磁盘使用量
-x:显示不是某一个档案系统的所有分割区磁盘使用量
-T:显示每个分割区所属的档案系统名称
-h: 表示使用「Human-readable」的输出,也就是在档案系统大小使用 GB、MB 等易读的格式。
    注释:
    例如:% df -hi

17. 查询档案或目录的磁盘使用空间
    命令:du (disk usage)
    格式:du [option] [filename]
    功能:以指定的目录下的子目录为单位,显示每个目录内所有档案所占用的磁盘空间大小
    选项:
-a:显示全部目录和其次目录下的每个档案所占的磁盘空间
-b:大小用bytes来表示 (默认值为k bytes)
-c:最后再加上总计 (默认值)
-s:只显示各档案大小的总合
-x:只计算同属同一个档案系统的档案
-L:计算所有的档案大小
-h: 表示档案系统大小使用 GB、MB 等易读的格式。
    例如:% du -a
% du -sh /etc 只显示该目录的总合
% du /etc | sort -nr | more 统计结果用sort 指令进行排序,
sort 的参数 -nr 表示要以数字排序法进行反向排序。

18. 显示进程
    命令:ps
    格式:ps [option]
    功能:显示系统中进程的信息。包括进程ID、控制进程终端、执行时间和命令。
    选项:
  -a 显示所有进程信息
  -U uidlist 列出这个用户的所有进程
          -e 显示当前运行的每一个进程信息
          -f 显示一个完整的列表
  -x 显示包括没有终端控制的进程状况 。
    注释:
    例如:% ps -ef
  % ps -aux 然后再利用一个管道符号导向到grep去查找特定的进程,然后再对特定的进程进行操作。

19. 终止进程
    命令:kill
    格式:kill [option] pid
    功能:向指定的进程送信号或终止进程。kill指令的用途是送一个signal给某一个process,
    因为大部份送的都是用来杀掉 process 的 SIGKILL 或 SIGHUP ,因此称为 kill 
    选项:-9  强行终止进程
    注释:pid标示进程号,可由ps命令得到。
    例如:% kill -9 pid
    你也可以用 kill -l 来察看可代替 signal 号码的数目字。kill 的详细情形请参阅 man kill。

20. 查看自己的IP地址
    命令:ifconfig
    格式:ifconfig -a
  
21. 查看路由表
    命令:netstat
    格式:netstat -rn

22. 远程登录
    命令:telnet
    格式:telnet hostname

23. 文件传输
    命令:ftp (file transfer program)
    格式:ftp hostname
    功能:网络文件传输及远程操作。
    选项:ftp命令:
           cd [dirname]  进入远程机的目录
           lcd [dirname] 设置本地机的目录
           dir/ls        显示远程的目录文件
           bin           以二进制方式进行传输
   asc           以文本文件方式进行传输
           get/mget      从远程机取一个或多个文件
           put/mput      向远程机送一个或多个文件
           prompt        打开或关闭多个文件传送时的交互提示
           close         关闭与远程机的连接
           quit          退出ftp
   !/exit ftp登陆状态下,!表示暂时退出ftp状态回到本地目录,exit表示返回ftp状态
    注释:
    例如:% ftp hostname

24. 查看自己的电子邮件
    命令:mailx
    格式:mailx
    选项:
delete  删除
next    下一个
quit    退出
         reply   回复   

25. 回忆命令
    命令:history
    格式:history
    功能:帮助用户回忆执行过的命令。
    选项:
    注释:
    例如:% history

26. 网上对话
    命令:talk
    格式:talk username
    功能:在网上与另一用户进行对话。
    选项:
    注释:对话时系统把终端分为上下两部分,上半部显示自己键入信息,下半部
          显示对方用户键入的信息。键入delete或Ctrl+C则结束对话。
    例如:% talk username

27. 允许或拒绝接受信息
    命令:mesg (message)
    格式:mesg [n/y]
    功能:允许或拒绝其它用户向自己所用的终端发送信息。
    选项:n 拒绝其它用户向自己所用的终端写信息
          y 允许其它用户向自己所用的终端写信息(缺省值)
    注释:
    例如:% mesg n

28. 给其他用户写信息
    命令:write
    格式:write username [ttyname]
    功能:给其他用户的终端写信息。
    选项:
    注释:若对方没有拒绝,两用户可进行交谈,键入EOF或Ctrl+C则结束对话。
    例如:write username

29. 创建、修改、删除用户和群组
    a. 创建群组:
例如: groupadd oinstall    创建群组名为oinstall的组
groupadd -g 344 dba 
创建组号是344的组,此时在/etc/passwd文件中产生一个组ID(GID)是344的项目。
    b. 修改群组:
groupmod:该命令用于改变用户组帐号的属性
groupmod –g 新的GID 用户组帐号名
groupmod –n 新组名 原组名:此命令由于改变用户组的名称

    c. 删除群组:
groupdel 组名:该命令用于删除指定的组帐号

    d. 新建用户:
命令: useradd [-d home] [-s shell] [-c comment] [-m [-k template]]
[-f inactive] [-e expire ] [-p passwd] [-r] name
主要参数
-c:加上备注文字,备注文字保存在passwd的备注栏中。 
-d:指定用户登入时的启始目录。
-D:变更预设值。
-e:指定账号的有效期限,缺省表示永久有效。
-f:指定在密码过期后多少天即关闭该账号。
-g:指定用户所属的群组。
-G:指定用户所属的附加群组。
-m:自动建立用户的登入目录。
-M:不要自动建立用户的登入目录。
-n:取消建立以用户名称为名的群组。
-r:建立系统账号。
-s:指定用户登入后所使用的shell。
-u:指定用户ID号。

举例: # useradd -g oinstall -G dba oracle  创建Oracle用户
   
    e. 删除用户
命令: userdel 用户名
删除指定的用户帐号
userdel –r 用户名(userdel 用户名;rm 用户名):删除指定的用户帐号及宿主目录
例:#useradd -g root kkk //把kkk用户加入root组里

    f. 修改用户
命令: usermod
修改已有用户的信息
usermod –l 旧用户名 新用户名: 修改用户名
usermod –L 用户名: 用于锁定指定用户账号,使其不能登陆系统
usermod –U 用户名: 对锁定的用户帐号进行解锁
passwd –d 用户名: 使帐号无口令,即用户不需要口令就能登录系统
例:#usermod -l user2 user1 //把用户user2改名为user1

30. 启动、关闭防火墙
永久打开或则关闭
chkconfig iptables on
chkconfig iptables off
即时生效:重启后还原
service iptables start
service iptables stop
     或者:
/etc/init.d/iptables start
/etc/init.d/iptables stop

31. 启动VSFTP服务
即时启动: /etc/init.d/vsftpd start
即时停止: /etc/init.d/vsftpd stop

开机默认VSFTP服务自动启动:
方法一:(常用\方便)
[root@localhost etc]# chkconfig --list|grep vsftpd ( 查看情况)
vsftpd          0:off   1:off   2:off   3:off   4:off   5:off   6:off
[root@localhost etc]# chkconfig vsftpd on  (执行ON设置)
或者:方法二:
修改文件 /etc/rc.local , 把行/usr/local/sbin/vsftpd & 插入文件中,以实现开机自动启动。

32. vi技巧
a. 进入输入模式
新增 (append)
a :从光标所在位置後面开始新增资料,光标後的资料随新增资料向後移动。
A:从光标所在列最後面的地方开始新增资料。

插入 (insert)
i:从光标所在位置前面开始插入资料,光标後的资料随新增资料向後移动。
I :从光标所在列的第一个非空白字元前面开始插入资料。

开始 (open)
o :在光标所在列下新增一列并进入输入模式。
O: 在光标所在列上方新增一列并进入输入模式。
b. 退出vi
在指令模式下键入:q,:q!,:wq或:x(注意:号),就会退出vi。其中:wq和:x是存盘退出,而:q是直接退出,如果文件已有新的变化,vi会提示你保存文件而:q命令也会失效,这时你可以用:w命令保存文件后再用:q 退出,或用:wq或:x命令退出,如果你不想保存改变后的文件,你就需要用:q!命令,这个命令将不保存文件而直接退出vi。

c. 删除与修改文件的命令:
x:删除光标所在字符。
dd :删除光标所在的列。
r :修改光标所在字元,r 後接著要修正的字符。
R:进入取替换状态,新增文字会覆盖原先文字,直到按 [ESC] 回到指令模式下为止。
s:删除光标所在字元,并进入输入模式。
S:删除光标所在的列,并进入输入模式。

d. 屏幕翻滚类命令
Ctrl+u: 向文件首翻半屏
Ctrl+d: 向文件尾翻半屏
Ctrl+f: 向文件尾翻一屏
Ctrl+b: 向文件首翻一屏
nz: 将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部。

e. 删除命令
ndw或ndW: 删除光标处开始及其后的n-1个字
do: 删至行首
d$: 删至行尾
ndd: 删除当前行及其后n-1行
x或X: 删除一个字符,x删除光标后的,而X删除光标前的
Ctrl+u: 删除输入方式下所输入的文本

f. 搜索及替换命令
/pattern: 从光标开始处向文件尾搜索pattern
?pattern: 从光标开始处向文件首搜索pattern
n: 在同一方向重复上一次搜索命令
N: 在反方向上重复上一次搜索命令
:s/p1/p2/g: 将当前行中所有p1均用p2替代
:n1,n2s/p1/p2/g: 将第n1至n2行中所有p1均用p2替代
:g/p1/s//p2/g: 将文件中所有p1均用p2替换

g. 复制,黏贴
(1) 选定文本块,使用v进入可视模式;移动光标键选定内容
(2) 复制选定块到缓冲区,用y;复制整行,用yy
(3) 剪切选定块到缓冲区,用d;剪切整行用dd
(4) 粘贴缓冲区中的内容,用p

h. 其他
在同一编辑窗打开第二个文件,用:sp [filename]
在多个编辑文件之间切换,用Ctrl+w

Linux查看进程命令

阅读数 99125