我们已经提到过一个其父进程终止的进程被称为一个孤儿,被init进程收养。我们现在看下可以被孤立的整个进程组以及POSIX.1如何处理这种情况。
考
虑一个fork一个子进程然后终止的进程。尽管这不是什么异常的事(它一直都发生),然而如果当父进程终止时子进程(使用工作控制)被停止了会发生什么?
子进程要怎样被继续,子进程知道它已经被孤立了吗?下面的代码展示了这个状况:父进程调用fork创建了一个停止的子进程,然后这个父进程准备退出:
- #include <errno.h>
- #include <stdio.h>
- #include <unistd.h>
- #include <signal.h>
- static void
- sig_hup(int signo)
- {
- printf("ISGHUP 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) {
- printf("fork error");
- exit(1);
- } else if (pid > 0) { /* parent */
- sleep(5); /* sleep to let child stop itself */
- exit(0); /* then parent exits */
- } else {
- 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);
- }
- }
这个程序有一些新的特性。这里,我们假定了一个工作控制外壳。回想下上节外壳把前台进程放入它自己的进程组里,而外壳停留在它自己的进程组里。子进程继承了它父进程的进程组。在fork后:
1、父进程睡眠了5秒。这是我们的(不完美的)方式来使子进程在父进程终止前执行;
2、子进程为挂起信号(SIGHUP)建立了一个信号处理器。这让我们可以看到SIGHUP是否被发送给了子进程。(我们将在第10章讨论信号处理。)
3、子进程给它自己发送停止信号(SIGTSTP),用kill函数。这停止了子进程,和我们用终端挂起字符(Control-Z)停止一个前台工作相似。
4、当父进程终止时,子进程被孤立,所以子进程的父进程ID变为1,也就是init进程。
5、
此时,子进程是一个孤立进程组的一个成员。一个孤立进程组的POSIX.1定义是,在这个组里所有成员的父进程或者是该成员本身,或者不是组会话的一个成
员。换一种说法就是,只要这个组的某个进程有一个父进程在相同会话的另一个组里,这个进程组就不是孤立的。如果进程组不是孤立的,就有可能在同一会话不同
进程组里的这些父进程的某个会重启非孤立进程组里的一个停止的进程。这里,这个组里的所有进程的父进程(例如,子进程的父进程的ID为1)都属于另一个会
话。
6、既然当父进程终止时进程组被孤立,POSIX.1要求向新的孤立进程里的每个停止的进程(如我们的子进程)发送挂起信号(SIGHUP),随后再发送继续信号(SIGCONT)。
7、这导致子进程在处理完挂起信号后被继续。挂起信号的默认行为是终止这个进程,所以我们必须提供一个捕获这个信号的信号处理器。我们因此预料sig_hup函数里的printf在pr_ids里的printf之前出现。
下面是程序的运行结果:
parent: pid = 3691, ppid = 2588, pgrp = 3691, tpgrp = 3691
child: pid = 3692, ppid = 3691, pgrp = 3691, tpgrp = 3691
ISGHUP received, pid = 3692
child: pid = 3692, ppid = 1, pgrp = 3691, tpgrp = 2588
read error from controlling TTY, errno = 5
正如我们意料的,子进程的父进程ID变为1.
在
子进程里调用pr_ids后,程序尝试从标准输入里读取。正如我们在这章更早时看到的,当一个后台进程组尝试从它的控制终端读取时,SIGTTIN会为这
个后台进程组产生。但是这里我们有一个孤立进程组;如果内核要用这个信号停止它的话,这个进程组里的进程将很有可能不再继续。POSIX.1规定在这种情
况下read要返回一个错误,errno被设为EIO(在这个系统上的值为5)。
最后,注意当父进程终止时,我们的子进程被放在一个后台进程组里,因为父进程作为一个前台工作被外壳执行。
我们将在19.5节里的pty程序里看到另一个孤立进程组的例子。