Linux02:进程管理

进程管理

进程描写叙述符及任务结构

    进程存放在叫做任务队列(tasklist)的双向循环链表中。链表中的每一项包括一个详细进程的全部信息,类型为task_struct,称为进程描写叙述符(process descriptor),该结构定义在<linux/sched.h>文件里。

    Linux通过slab分配器分配task_struct结构,这样能达到对象复用和缓存着色(cache coloring)的目的。还有一方面,为了避免使用额外的寄存器存储专门记录,让像x86这样寄存器较少的硬件体系结构仅仅要通过栈指针就能计算出task_struct的位置,该结构为thread_info,在文件<asm/thread_info.h>中定义。

Linux中能够用ps命令查看全部进程的信息。

进程状态

task_struct中的state描写叙述进程的当前状态。进程的状态一共同拥有5种,而进程必定处于当中一种状态:

    1)TASK_RUNNING(运行)——进程是可运行的,它或者正在运行,或者在运行队列中等待运行。这是进程在用户空间中运行唯一可能的状态;也能够应用到内核空间中正在运行的进程。

    2)TASK_INTERRUPTIBLE(可中断)——进程正在睡眠(也就是说它被堵塞)等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为执行,处于此状态的进程也会由于接收到信号而提前被唤醒并投入执行。

    3)TASK_UNINTERRUPTIBLE(不可中断)——除了不会由于接收到信号而被唤醒从而投入执行外,这个状态与可打断状态同样。这个状态通常在进程必须在等待时不受干扰或等待事件非常快就会发生时出现。由于处于此状态的任务对信号不作响应,所以较之可中断状态,使用得较少。

    4)TASK_ZOMBIE(僵死)——该进程已经结束了,可是其父进程还没有调用wait4()系统调用。为了父进程可以获知它的消息,子进程的进程描写叙述符仍然被保留着。一旦父进程调用了wait4(),进程描写叙述符就会被释放。

    5)TASK_STOPPED(停止)——进程停止执行,进程没有投入执行也不能投入执行。通常这样的状态发生在接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号的时候。此外,在调试期间接收到不论什么信号,都会使进程进入这样的状态。

    须要调整进程的状态,最好使用set_task_state(task, state)函数,在必要的时候,它会设置内存屏障来强制其它处理器作又一次排序(SMP)。

进程的各个状态之间的转化构成了进程的整个生命周期,

 

进程的创建

         在Linux系统中,全部的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本(initscript)并运行其它的相关程序,终于完毕系统启动的整个进程。

Linux提供两个函数去处理进程的创建和运行:fork()和exec()。首先,fork()通过拷贝当前进程创建一个子进程。子进程与父进程的差别只在于PID(每一个进程唯一),PPID(父进程的PID)和某些资源和统计量(比如挂起的信号)。exec()函数负责读取可运行文件并将其加载地址空间開始运行。

        fork()使用写时拷贝(copy-on-write)页实现。内核在fork进程时不复制整个进程地址空间,让父进程和子进程共享同一个拷贝,当须要写入时,数据才会被复制,使各进程拥有自己的拷贝。在页根本不会被写入的情况下(fork()后马上exec()),fork的实际开销仅仅有复制父进程的页表以及给子进程创建唯一的task_struct。

       创建进程的fork()函数实际上终于是调用clone()函数。创建线程和进程的步骤一样,仅仅是终于传给clone()函数的參数不同。比方,通过一个普通的fork来创建进程,相当于:clone(SIGCHLD, 0);创建一个和父进程共享地址空间,文件系统资源,文件描写叙述符和信号处理程序的进程,即一个线程:clone(CLONE_VM | CLONE_FS | CLONE_FILES |CLONE_SIGHAND, 0)。

      在内核中创建的内核线程与普通的进程之间还有个主要差别在于:内核线程没有独立的地址空间,它们仅仅能在内核空间执行。

fork和vfork的差别

fork()与vfock()都是创建一个进程,那他们有什么差别呢?总结有下面三点差别: 
1.  fork  :子进程拷贝父进程的数据段,代码段 
     vfork:子进程与父进程共享数据段 
2.  fork :父子进程的运行次序不确定 
     vfork 保证子进程先执行,在调用exec 或exit 之前与父进程数据是共享的,在它调用exec
     或exit 之后父进程才可能被调度执行。 
3.  vfork ()保证子进程先执行,在她调用exec 或exit 之后父进程才可能被调度执行。假设在
     调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。 

进程终止

      进程在执行结束,或接受到它既不能处理也不能忽略的信号,或异常时,都会被终结。此时,依靠do_exit()(在kernel/exit.c文件里)把与进程相关联的全部资源都被释放掉(如果进程是这些资源的唯一使用者)。至此,与进程相关的全部资源都被释放掉了。进程不可执行(实际上也没有地址空间让它执行)并处于TASK_ZOMBIE状态。它占用的全部资源就是内核栈、thread_info和task_struct。此时进程存在的唯一目的就是想它的父进程提供信息。在父进程获得已终结的子进程的信息后,或者通知内核它并不关注那些信息后,子进程持有的task_struct等剩余内存才被释放。

孤儿进程问题

      假设父进程在子进程之前退出,必须有机制保证子进程能找到一个新的父类,否则的话这些成为孤儿的进程就会在退出时永远处于僵死状态,白白的耗费内存。解决方法是给子进程在当前线程组内找一个线程作为父亲,假设不行,就让init做它们的父进程。

猜你喜欢

转载自blog.csdn.net/xuanying_china/article/details/81176871
今日推荐