孤立进程组(Orphaned Process Groups)

孤立进程组(Orphaned Process Groups)

之前我们说过如果一个进程的父进程终止了,那么它会“孤立”并且由init进程继承管理。现在我们看一下所有能被“孤立”的进程组,还有POSIX.1如何处理这种状态。

例如:

考虑一下这种情况,一个进程fork一个子进程,然后终止。虽然这没有什么不正常的(它可能发生在任何时候),但当父进程终止时,如果子进程已经停止(使用任务控制)会发生什么?子进程要如何继续运行,还有如何让子进程知道自己已经成“孤儿”了么?下图显示了这个状态:父进程已经fork了一个子进程,子进程停止,之后父进程打算退出。

figure9-10

下面的程序(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会返回一个errnoEIO的错误(在本系统上它的值是5)。

最后,注意一下当父进程终止时,我们的子进程已经被放到后台进程组中,因为父进程是被shell当做前台进程执行的。

Comments are closed.