3 进程
文章目录
- 如果16个用户同时运行,则就有16个独立的进程(尽管它们共享同一可执行代码)
- Linux源代码中,把进程称为task或线程(thread)。
- 这章先论进程的静态特性
- 然后描述内核如何进程切换
- 最后两节:创建和撤消进程。
- 这章:Linux对多线程应用程序的支持,
- 如一章提到的,它依赖所谓的轻量级进程LWP。
进程、轻量级进程和线程
- 术语“进程”常有几个不同的含义。
- 本书遵循OS教科书中的通常定义:进程是程序执行时的一个实例。
- 可把它看作充分描述程序已经执行到何种程度的数据结构的汇集
- 进程类似人类:它们被产生,有或多或少有效的生命,可以产生一个或多个子进程
最终都要死亡。 - 一个微小的差异是进程之间没有性别差异一一每个进程都只有一个父亲
- 从内核观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的实体。
- 当一个进程创建时,它几乎与父进程相同。
- 它接受父进程地址空间的一个(逻辑)拷贝,并从进程创建系统调用的下一条指令开始执行与父进程相同的代码。
- 父子进程可共享含有程序代码(正文)的页,但它们各自有独立的数据拷贝(栈和堆),因此子对一个内存单元的修改对父是不可见的(反之亦然)。
- 早期Unix内核用这种简单模式,但现代Unix系统并没有如此使用。
- 它们支持多线程应用程序——————拥有很多相对独立执行流的用户程序共享应用程序的大部分数据结构。
- 在这样的系统中,一个进程由几个用户线程(或简单地说,线程)组成,每个线程都代表进程的一个执行流。
- 现在,大部分多线程应用程序都用 pthread( POSIX thread)库的标准库函数集编写的
- Linux内核早期版本没提供多线程应用的支持。
- 从内核观点看,多线程应用程序仅是个普通进程。
- 多线程应用程序多个执行流的创建、处理、调度整个都是在用户态进行的(使用 POSIX兼容的 pthread库)。
- 但这种多线程应用程序的实现方式不令人满意。
- 一个象棋程序用两线程:一个控制图形化棋盘,等待人类选手的移动并显示计算机的移动,另个思考棋的下一步移动。
- 尽管第一个线程等待选手移动时,第二个线程应当继续运行,以此利用选手的思考时间。
- 如果象棋程序仅是个单独的进程,第一个线程就不能简单地发出等待用户行为的阻塞系统调用,否则,第二个线程也被阻塞。
- 第一个线程须使用复杂的非阻塞技术来确保进程仍可运行的
- Linux用lightweight process对多线程应用程序提供更好支持。
- 两个轻量级进程基本可共享一些资源,如地址空间、打开的文件。
- 只要其中一个修改共享资源,另个就立即査看这种修改。
- 当然,当两个线程访问共享资源时必须同步它们自己。
- 实现多线程应用程序的一个简单方式就是把轻量级进程与每个线程关联起来。
- 这样,线程之间就可通过简单地共享同一内存地址空间、同一打开文件集等来访问相同的应用
程序数据结构集;同时,每个线程都可由内核独立调度,以便一个睡眠的同时另一个仍可运行。 - POSIX兼容的pthread库用Linux轻量级进程有3个例子,它们是
Linuxthreads、 Native Posix Thread Library(NPTL)和IBM的下一代 Posix线程包NGPT
(Next Generation Posix Threading Package)
- POSIX兼容的多线程应用程序由支持“线程组”的内核来处理最好不过。
- Linux中,一个线程组基本上就是实现了多线程应用的一组轻量级进程,对于像 getpid(),ki11(),
和_exit()这样的系统调用,它像一个组织,起整体的作用
进程描述符
- 为管理进程,内核须对每个进程所做的事情进行描述。
- 内核须知道进程的优先级,它是正在CPU上运行还是因某些事被阻塞,给它分配了什么样的地址空间,允许它访问哪个文件
- 这正是process descriptor的作用
- 进程描述符都是task_ struct类型结构,
- 字段包含了与一个进程相关的所有信息(注1)。
- 进程描述符中存放了那么多信息,所以它很复杂。
- 包含了很多进程属性,且一些字段还包括了指向其他数据结构的指针
- 3-1:Linux的进程描述符。
- 图右边的六个数据结构涉及进程拥有的特殊资源,将在以后的章节中涉及到。
- 本章论两字段:进程的状态和进程的父/子关系。
进程状态
- state
- 由一组标志组成,其中每个标志描述一种可能的进程状态。
- 当前的Linux版本中,这些状态是互斥的,因此,只能设置一种状态;其余的标志将被清除。
- 下面是进程可能的状态
- (TASK_ RUNNING)
- 要么在CPU上执行,要么准备执行。
- 可中断的等待状态(TASK_ INTERRUPTIBLE)
- 进程被挂起(睡眠),直到某条件变为真。
- 产生一个硬件中断,释放进程正等待的系统资源,或传递一个信号都是可以唤醒进程的条件(把进程的状态回到TASK_ RUNNING)。
- 不可中断的等待状态(TASK_UNエ NTERRUPTTBLE
与可中断的等待状态类似,但有一个例外,把信号传递到睡眠进程不能改变它的状
态。这种状态很少用到,但在一些特定的情况下(进程必须等待,直到一个不能被
中断的事件发生),这种状态是很有用的。例如,当进程打开一个设备文件,其相
应的设备驱动程序开始探测相应的硬件设备时会用到这种状态。探测完成以前,设
备駆动程序不能被中断,否则,硬件设备会处于不可预知的状态。
暂停状态(TASK_ STOPPED)
进程的执行被暂停。当进程接收到 SIGSTOP、 SIGTSTP、 SIGTTIN或 SIGTTOU
信号后,进入暂停状态。
- 跟踪状态(TASK_ TRACED
- 进程的执行已由 debugger程序暂停。当一个进程被另一个进程监控时(例如
debugger+执行 ptrace()系统调用监控一个测试程序),任何信号都可以把这个进
程置于TASK_ TRACED状态。
- 还有两个状态是既可存放在进程描述符的 state中,也可存放在exit_ state中。
- 从这两个字段的名称可以看出,只有当进程的执行被终止时,进程的状态才会变为这两种状态中的一种
- 僵死状态(EXIT_ ZOMBIE)
- 进程的执行被终止,但父还没有发布wait4()或 waitpid()系统调用来返回有关死亡进程的信息(注2)。
- 发布wait()类系统调用前,内核不能丢弃包含在死进程描述符中的数据,因为父进程可能还需要它(参见本章结尾的“进程
除”一节)。
- 注2:还有其他wait()类的库函数,如Wait3()和wait(),但Linux中它们是靠wait4()和waitpid()系调用来实现
- 僵死撤消状态(EXT_DEAD)
- 最终状态:由于父发出wait4()或 waitpid()系统调用,因而进程由系统删除。
- 为防止其他执行线程在同一个进程上也执行wait()类系统调用(这是种竞争条件),而把进程的状态由僵死(EXIT_ ZOMBIE)状态改为僵死撤消状态
(EX工T_DEAD)(见五章)。
- state的值常用赋值语句设置。
- 例如:
- p->state = TASK RUNNING
- 内核也用set_task_state,和set_ current_ state宏:分别设置指定进程的状态和当前执行进程的状态。
- 此外,这些宏确保编译程序或CPU控制单元不把赋值操作与其他指令混合。
- 混合指令的顺序有时会导致灾难性的后果(见五章)。