孤立进程组(Orphaned Process Groups)
之前我们说过如果一个进程的父进程终止了,那么它会“孤立”并且由init进程继承管理。现在我们看一下所有能被“孤立”的进程组,还有POSIX.1如何处理这种状态。
例如:
考虑一下这种情况,一个进程fork一个子进程,然后终止。虽然这没有什么不正常的(它可能发生在任何时候),但当父进程终止时,如果子进程已经停止(使用任务控制)会发生什么?子进程要如何继续运行,还有如何让子进程知道自己已经成“孤儿”了么?下图显示了这个状态:父进程已经fork了一个子进程,子进程停止,之后父进程打算退出。
下面的程序(FIGURE 9.11)演示了这种情况。这个程序有一些新功能。这里我们假定用的SHELL支持任务控制。回忆之前的章节,SHELL把前台进程组放进它自己的进程组(本例是6099)中,并且SHELL留在它自己的进程组(2837)中。子进程从它的父进程那里继承了进程组(6099)。然后fork,
Figure 9.11. Creating an orphaned process group#include "apue.h"
#include <errno.h>
static void
sig_hup(int signo)
{
printf("SIGHUP received, pid = %d\n", getpid());
}
static void
pr_ids(char *name)
{
printf("%s: pid = %d, ppid = %d, pgrp = %d, tpgrp = %d\n",
name, getpid(), getppid(), getpgrp(), tcgetpgrp(STDIN_FILENO));
fflush(stdout);
}
int
main(void)
{
char c;
pid_t pid;
pr_ids("parent");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid > 0) { /* parent */
sleep(5); /*sleep to let child stop itself */
exit(0); /* then parent exits */
} else { /* child */
pr_ids("child");
signal(SIGHUP, sig_hup); /* establish signal handler */
kill(getpid(), SIGTSTP); /* stop ourself */
pr_ids("child"); /* prints only if we're continued */
if (read(STDIN_FILENO, &c, 1) != 1)
printf("read error from controlling TTY, errno = %d\n",
errno);
exit(0);
}
}
|
- 父进程休眠5秒。这是我们让子进程在父进程之前执行的一种方法。(imperfect)
- 子进程为挂起信号(SIGHUP)建立信号句柄。这能让我们看到SIGHUP是否已经发送到子进程。
- 子进程使用kill程序发送停止信号(SIGTSTP)。这里停止子进程类似于我们使有终端的挂起字符(Ctrl+Z)停止前台任务。
- 当父进程结束,子进程变成“孤岛”,所以子进程的父进程变成1——init进程的ID。
- 现在,子进程是孤岛进程组的成员了。POSIX.1定义的独立进程组是:one in which the parent of every member is either itself a member of the group or is not a member of the groups session。换种说法就是:只要进程组中的进程它有一个在同一会话不同进程组的父进程,那么它就不是孤岛进程组。如果进程组不是“孤岛”,那么它的父进程就有机会重新启动已经停止的进程。在这里,进程组中的每个进程的父进程(也就是进程1是进程6100的父进程)都属于其它会话。
- 因为当父进程终止时,进程组变成“孤岛”,所以POSIX.1需要每一个进程,该进程是新近的已经停止(一个挂起信号“SIGHUP”跟着一个继续信号“SIGCONT”)的孤岛进程组(就像我们的子进程)。
- 这能引起在进程处理挂起信号后,子进程继续。对于挂起信号默认动作是终止进程,所以我们必须提供信号句柄,用于捕捉信号。因此我们希望在sig_hup函数中的printf函数在pr_ids函数中的printf之前显示。
这是上面函数的输出:
$ ./a.out
parent: pid = 6099, ppid = 2837, pgrp = 6099, tpgrp = 6099
child: pid = 6100, ppid = 6099, pgrp = 6099, tpgrp = 6099
$ SIGHUP received, pid = 6100
child: pid = 6100, ppid = 1, pgrp = 6099, tpgrp = 2837
read error from controlling TTY, errno = 5
|
注意:来自子进程的shell输出提示,因为两个进程都是我们登陆shell打开的,并且子进程写到终端。就像我们希望的那样,子进程的父进程ID变成了1。
在子进程中调用pr_ids后,程序试图从标准输入读取数据。就像之前看到的那样,当后台进程组试图从它的控制终端中读取时,会产生一个SIGTTIN信号给后台进程组。但是在这里我们有一个孤岛进程组;如果内核使用这个信号停止进程,那么进程组中的该进程可能永远都不能恢复运行了。POSIX.1指定这种情况下,read会返回一个errno为EIO的错误(在本系统上它的值是5)。
最后,注意一下当父进程终止时,我们的子进程已经被放到后台进程组中,因为父进程是被shell当做前台进程执行的。