1. 为什么会有孤儿进程组的概念,APUE没直接写,但是GNU有规定:
孤儿进程组不可以获得终端,这是为了保证控制进程死掉后他的终端可以安全分配给新session。posix要求向新孤儿进程组中停止状态的进程(GNU说是给孤儿进程组里所有进程发消息,而APUE中文版说是给停止状态的进程发消息)发送SIGHUP(挂起)信号和SIGCONT(继续)信号。首先处理SIGHUP信号,系统默认处理是终止进程,然而也可以另行处理这样进程会继续执行,但任不可以再获得终端。
2. 书本代码示例apue.3e/relation/orphan3.c较费解
#include "apue.h" #include <errno.h> static void sig_hup(int signo) { printf("SIGHUP received, pid = %ld\n", (long)getpid()); } static void pr_ids(char *name) { printf("%s: pid = %ld, ppid = %ld, pgrp = %ld, tpgrp = %ld\n", name, (long)getpid(), (long)getppid(), (long)getpgrp(), (long)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 */ } 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 %d on controlling TTY\n", errno); } exit(0); }
上面代码,father等待5秒后狗带,child运行结果如下:
[karenyin@host relation]$ ./orphan3 parent: pid = 15877, ppid = 15811, pgrp = 15877, tpgrp = 15877 // father是组长,也是前台进程组ID,father的father是shell,shell pid=15811 child: pid = 15878, ppid = 15877, pgrp = 15877, tpgrp = 15877 // child是组员,当前这个进程组占据了终端。结果输出在这里停了几秒,然后才打印下一句“SIGHUP received” [karenyin@host relation]$ SIGHUP received, pid = 15878 // 进程组先退出终端,然后又在终端打印SIGHUP child: pid = 15878, ppid = 1, pgrp = 15877, tpgrp = 15811 // 前台进程组已经不是father了,说明终端又交给了shell read error 5 on controlling TTY // child试图读标准输入read(STDIN_FILENO, &c, 1),触发了异常。
^C // child没有走到exit(0)
原因分析如下:
1. kill(15878, SIGTSTP)并不是要child杀死自己,而是暂停前台作业。
2. father自然死亡后,child成为孤儿进程,被init收养,因此ppid=1。
child也成为进程组15877也成为孤儿进程组,因此收到SIGHUP信号(之后还会收到SIGCONT信号,表示继续)。
3. 子进程处理SIGHUP信号的时候,已经是孤儿进程组成员了,没有权限刷到标准输出,为什么成功打印了“SIGHUP received, pid = 15878”呢?同样的疑惑,为什么child能在终端打印“read error %d on controlling TTY\n”呢?关于这个疑问其实apue也有写,POSIX.1只对读控制终端作出限制。
所以说,gnu对孤儿进程组的定义里access一词的意思是“读”,孤儿进程组成员试图读取控制终端时才抛EIO,对写终端未做规定。
小结
这个故事说明,像W.Richard Stevens这种咖位的技术书籍作者,写书不会含糊其辞的。看书的时候不要自信跳过,不然有可能浪费时间。