进程间关系

进程间关系:进程、僵尸进程、孤儿进程、进程组、前台进程组、后台进程组、孤儿进程组、会话、控制终端

进程属于一个进程组,进程组属于一个会话,会话可能有也可能没有控制终端[1]
(lesca原创,转载请注明转自http://lesca.me)

僵尸进程与孤儿进程
僵尸进程:先于父进程终止,但是父进程没有对其进行善后处理(获取终止子进程有关信息,释放它仍占有的资源)。消灭僵尸进程的唯一方法是终止其父进程。
孤儿进程:该进程的父进程先于自身终止。其特点是PPID=1(init进程的ID)。一个孤儿进程可以自成孤儿进程组。

文中用到的缩写
PID = 进程ID (由内核根据延迟重用算法生成)
PPID = 父进程ID(只能由内核修改)
PGID = 进程组ID(子进程、父进程都能修改)
SID = 会话ID(进程自身可以修改,但有限制,详见下文)
TPGID= 控制终端进程组ID(由控制终端修改,用于指示当前前台进程组)

进程、进程组、会话之间的关系
进程属于一个进程组,进程组属于一个会话,会话可能有也可能没有控制终端[1]
会话首进程:新建会话时,会话中的唯一进程,其PID=SID。它通常是一个登陆shell,也可以在成为孤儿进程后调用setsid()成为一个新会话。
会话:一个或多个进程组的集合。一个登陆shell发起的会话,一般由一个会话首进程、一个前台进程组、一个后台进程组组成。
进程组:一个或多个进程的集合,进程组属于一个会话。fork()并不改变进程组ID。
进程组组长:PID与PGID相等的进程。组长可以改变子进程的进程组ID,使其转移到另一进程组。例如一个shell进程(下文均以bash为例),当使用管道线时,例如echo "hello" | cat,bash以第一个命令的进程ID为该管道线内所有进程设置进程组ID。本例中echocat的进程组ID都设置成echo的进程ID[2]
前台进程组[3]:该进程组中的进程能够向终端设备进行读、写操作的进程组。
后台进程组[4]:一个会话中,除前台进程组、会话首进程以外的所有进程组。该进程组中的进程能够向终端设备,但是当试图终端设备时,将会收到SIGTTIN信号,并停止。登录shell可以根据设置在终端上发出一条消息[5]通知用户有进程欲求终端。
孤儿进程组(定义1):该组中的每个成员的父进程要么是该组的一个成员,要么不是该组所属会话的成员
孤儿进程组(定义2):不是孤儿进程组的条件是,该组中有一个进程,其父进程属于同一会话的另一个组中[6]

解析:产生一个僵尸进程(组)并读终端
1.由组长fork()产生的子进程其进程组ID不变(因为fork()不改变进程组ID)
2.若组长是bash,则将子进程的进程组ID设置成第一个命令的PID,即由第一个命令当组长,并成为一个新的进程组
3.由bash产生的新进程组中,至少要有一个进程的PPID指向该bash,否则该进程组成为孤儿进程组[6],无法将进程状态的改变通知bash
4.bash通过wait函数族检测子进程(新的进程组)的状态,从而决定如何设置前台进程组ID(给指定的终端设备)[3]
5.后台进程(组)试图控制终端设备时,终端驱动程序向其发送SIGTTIN信号,此时应当由bash唤醒该进程(组),使之进入前台
6.对于孤儿进程(组),bash无法知晓其状态,因为bash不知道其PID,而唯一知道其PID的进程已经终止,也就无法知晓其组ID,从而不能将其组ID放入前台。如果孤儿进程(组)试图读取终端,read()调用将失败,并将errno置为EIO。

注释
[1] 在内核中,/dev/tty控制终端的同义语。
[2] 不同的shell对使用管道线时创建子进程的顺序不同,本文以bash为例,它是支持作业控制的shell的典型代表。
[3] 登陆shell(例如bash)通过调用tcsetpgrp()函数设置前台进程组,该函数将终端设备的fd(文件描述符)与指定进程组关联。成为前台进程组的进程其TPGID=PGID,常常可以通过比较他们来判断前后台进程组。
[4] 前台进程组ID只能有一个,而后台进程组同时可存在多个。后台进程组的PGID≠TPGID。
[5] 可以通过使用stty tostop命令禁止后台进程组向终端进行操作,当发出写请求时,将会收到SIGTTOU信号。
[6] 也就是说,将该父进程终止就能使该进程组成为僵尸进程。这个父进程通常是这个进程组的组长进程,因为只有它的父进程在这个进程组外,而其他进程(组长的子进程)的父进程都是组长进程的ID。

你可能感兴趣的:(算法,shell,bash,终端,作业)