在Linux的内核的五大组成模块中,进程管理模块时非常重要的一部分,它虽然不像内存管理、虚拟文件系统等模块那样复杂,也不像进程间通信模块那样条理化,但作为五大内核模块之一,进程管理对我们理解内核的运作、对于我们以后的编程非常重要。同时,作为五大组成模块中的核心模块,它与其他四个模块都有联系。下面就对进程模块进行想写的介绍,首先要了解进程及其相关的概念。其次介绍进程的创建、切换、撤销等基本操作。除此之外,还给出了Linux内核是如何对进程进行调度管理的。
一、进程及其相关概念
进程:进程可以理解为程序执行的一个实例,它包括可执行程序以及与其相关的系统资源,比如打开的文件、挂起的信号、内核内部数据、处理器状态、内存地址空间及包含全局变量的数据段等。从内核的角度看,进程也可以称为任务。
进程描述符:与进程相关的事情非常多,比如进程的状态、进程的优先级、进程的地址空间、允许该进程访问的文件等等,Linux内核为此专门设计了一个类型为task_struct的结构体,称之为进程描述符。进程描述符中包含了内核管理进程的所有信息,可以说,只要得到一个进程的进程描述符,就可以知道一个进程的所有信息。
进程状态:进程描述符task_struct结构体中有一个state字段,表示进程当前的所处状态。从进程的创建到进程的删除,它可以经过5种不同的状态,分别是可运行状态、可中断的等待状态、不可中断的等待状态、暂停状态、跟踪状态。除此之外,当进程被终止时,还可能会变为僵死状态、僵死撤消状态。内核可以使用宏set_current_state(state)设置当前进程的状态,用set_task_state(task,state)设置某进程的状态。
进程标示符:进程描述task_struct结构体中的pid字段可以标识唯一标识一个进程,称之为进程标识符PID。当创建一个新进程时,PID是按照顺序从小到大分配给新进程的。内核通过管理一个pidmap_array位图来表示当前已分配的PID和闲置的PID号。注意:在多线程组中,所有的线程共享相同的PID。除了进程标识符外,内核对进程的大部分访问时通过进程描述符指针进行的。
进程关系:进程之间的关系有亲属关系和非亲属关系。亲属关系包括父子关系和兄弟关系等。其中由tast_struct结构体中的parent/children/real_parent/sibling等字段描述。除了亲属关系外,还有其他关系,比如,一个进程是一个进程组或登录会话的领头进程,可能是一个线程组的领头进程,这些关系由group_leader/tgid/signal->pgrp等字段描述。
进程资源:为了防止进程过度的使用系统资源,内核为每个进程使用资源的数量进行了一些限制。其中包括进程地址空间的最大数、进程使用CPU的最大时间、堆的最大值、文件大小的最大值、文件锁数量的最大值、消息队列的最大字节数、打开文件描述符的最大数、进程拥有的页框最大数等。
二、进程的创建、切换、撤销
进程的创建:在Linux环境编程时,一般采用fork()函数来创建新的进程,当然,那是在用户空间的函数,它会调用内核中的clone()系统调用,由clone()函数继续调用do_fork()完成进程的创建。
传统Unix系统中,创建的子进程复制父进程所拥有的资源,这种方法效率低,因为子进程需要拷贝父进程的整个地址空间。但是,子进程几乎不必读或修改父进程拥有的所有资源,因为很多情况下,子进程创建后会立即调用exec()一族的函数,并清除父进程仔细拷贝过来的地址空间。现代Unix系统用三种方式解决了这个问题:1、写实复制技术允许父子进程读相同的物理页。2、轻量级进程允许父子进程共享每进程在内核的很多数据结构。3、vfork()系统调用创建的进程能共享父进程的内存地址空间,为了防止父进程重写子进程需要的数据,阻塞父进程的执行,一直到子进程退出或执行一个新的程序为止。整个进程创建过程可能涉及到如下函数:
fork()/vfork()/_clone----------->clone()--------->do_fork()---------->copy_process()
上面的创建过程结束之后,就有了处于可运行状态的完整的子进程,新的子进程有了PID、进程描述符等各种数据结构,要想实际运行它,还需要调度程序把CPU交给新创建的子进程。
除了进程外,还有内核线程(用kernet_thread创建)的概念。在Linux中,内核线程与普通进程存在以下两个方面的不同:
1、内核线程只运行在内核态,而普通进程既可以运行在内核态,也可运行在用户态。