分类: LINUX
2009-04-10 09:30:33
在中我说过“Shell可以同时运行一个前台进程和任意多个后台进程”其实是不全面的,现在我们来研究更复杂的情况。事实上,Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制(Job Control)。例如用以下命令启动5个进程:
$ proc1 | proc2 &
$ proc3 | proc4 | proc5
其中proc1
和proc2
属于同一个后台进程组,proc3
、proc4
、proc5
属于同一个前台进程组,Shell进程本身属于一个单独的进程组。这些进程组的控制终端相同,它们属于同一个Session。当用户在控制终端输入特殊的控制键(例如Ctrl-C)时,内核会发送相应的信号(例如SIGINT
)给前台进程组的所有进程。各进程、进程组、Session的关系如下图所示(该图出自)。
图 34.4. Session与进程组
现在我们从Session和进程组的角度重新来看登录和执行命令的过程。
getty
或telnetd
进程在打开终端设备之前调用setsid
函数创建一个新的Session,该进程称为Session Leader,该进程的id也可以看作Session的id,然后该进程打开终端设备作为这个Session中所有进程的控制终端。在创建新Session的同时也创建了一个新的进程组,该进程是这个进程组的Process Group Leader,该进程的id也是进程组的id。
在登录过程中,getty
或telnetd
进程变成login
,然后变成Shell,但仍然是同一个进程,仍然是Session Leader。
由Shell进程fork
出的子进程本来具有和Shell相同的Session、进程组和控制终端,但是Shell调用setpgid
函数将作业中的某个子进程指定为一个新进程组的Leader,然后调用setpgid
将该作业中的其它子进程也转移到这个进程组中。如果这个进程组需要在前台运行,就调用tcsetpgrp
函数将它设置为前台进程组,由于一个Session只能有一个前台进程组,所以Shell所在的进程组就自动变成后台进程组。
在上面的例子中,proc3
、proc4
、proc5
被Shell放到同一个前台进程组,其中有一个进程是该进程组的Leader,Shell调用wait
等待它们运行结束。一旦它们全部运行结束,Shell就调用tcsetpgrp
函数将自己提到前台继续接受命令。但是注意,如果proc3
、proc4
、proc5
中的某个进程又fork
出子进程,子进程也属于同一进程组,但是Shell并不知道子进程的存在,也不会调用wait
等待它结束。换句话说,proc3 | proc4 | proc5
是Shell的作业,而这个子进程不是,这是作业和进程组在概念上的区别。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程组还存在(如果这个子进程还没终止),则它自动变成后台进程组(回顾一下)。
下面看两个例子。
$ ps -o pid,ppid,pgrp,session,tpgid,comm | cat
PID PPID PGRP SESS TPGID COMMAND
6994 6989 6994 6994 8762 bash
8762 6994 8762 6994 8762 ps
8763 6994 8762 6994 8762 cat
这个作业由ps
和cat
两个进程组成,在前台运行。从PPID
列可以看出这两个进程的父进程是bash
。从PGRP
列可以看出,bash
在id为6994的进程组中,这个id等于bash
的进程id,所以它是进程组的Leader,而两个子进程在id为8762的进程组中,ps
是这个进程组的Leader。从SESS
可以看出三个进程都在同一Session中,bash
是Session Leader。从TPGID
可以看出,前台进程组的id是8762,也就是两个子进程所在的进程组。
$ ps -o pid,ppid,pgrp,session,tpgid,comm | cat &
[1] 8835
$ PID PPID PGRP SESS TPGID COMMAND
6994 6989 6994 6994 6994 bash
8834 6994 8834 6994 6994 ps
8835 6994 8834 6994 6994 cat
这个作业由ps
和cat
两个进程组成,在后台运行,bash
不等作业结束就打印提示信息[1] 8835
然后给出提示符接受新的命令,[1]
是作业的编号,如果同时运行多个作业可以用这个编号区分,8835是该作业中某个进程的id。请读者自己分析ps
命令的输出结果。