72-孤儿进程与孤儿进程组

之前有学习过僵尸进程,它指的是“未老先衰”的子进程先于父进程去逝,而父进程未对它进行回收(wait)所产生的。

本文来谈谈,另一个对立的概念——孤儿进程,以及孤儿进程组。

1. 孤儿进程

如果一个进程,它的父进程先终止了,则该进程成为孤儿进程。此后,该进程的父进程变为 1 号 init 进程。

下面的代码可以产生一个孤儿进程。

int main() {
  pid_t pid = fork();
  if (pid == 0) {
     while(1) sleep(1);
  }
  return 0;
}

上面的程序运行后,产生一个子进程,进入死循环,而父进程将会退出,此后子进程被 init 进程回收,成为孤儿进程。

需要注意的是,孤儿进程不像僵尸进程,它是无害的,也不需要回收。

2. 孤儿进程组

整个进程组也可以成为孤儿。POSIX.1 将孤儿进程组定义为:

该进程组的每个成员的父进程要么是该组的成员,要么在其它会话中。

只要能够满足上面的定义,则此进程组就是孤儿进程组。可能读起来比较拗口,看图 1 中的例子可能会清楚点。


72-孤儿进程与孤儿进程组_第1张图片
图 1 非孤儿进程组与孤儿进程组

图 1 中,按照孤儿进程组的定义,进程组 1 不是孤儿进程组,因为进程组 1 中有一个进程的父进程不属于进程组 1,也不在另一个会话中。

进程组 2 是孤儿进程组,因为该组中的每个成员满足定义:每个成员的父进程要么在本组中,要么在其它会话中。

3. 孤儿进程组的特性

如果一个进程组包含一个或一个以上的停止的进程,当该进程组变成孤儿进程组时,每个处于停止状态的进程会接收到 SIGHUP 挂断信号,紧接着又会收到 SIGCONT 信号。(以上是 APUE 中的解释)

注意,在英文原版中是这样叙述的:

Since the process group is orphaned when the parent terminates, and the process group contains a stopped process, POSIX.1 requires that every process in the newly orphaned process group be sent the hang-up signal (SIGHUP) followed by the continue signal (SIGCONT).

上面的大意是说,刚产生的孤独进程组中的每一个进程,而不是每一个停止的进程,是每一个进程都能收到 SIGHUP 信号。中文翻译版本的确有点坑爹啊!!!

在 Ubuntu 和 Red Hat 中的测试结果也验证了原版中的解释,如果进程组存在停止状态的进程,当该进程组变成孤儿进程组时,该进程组中所有进程都会收到 SIGHUP 信号。其它系统中并未测试,如果你的结果和此不一样,请在下文留言。

4. 实验

下面的程序功能:进程组中存在停止的进程,该进程组成为孤儿进程组后,该进程组所有的进程会收到 SIGHUP 信号。另外,该程序还演示了后台进程组试图读控制终端会产生错误。

需要注意的是,父进程终止,子进程进入后台进程组。

  • 代码
// orphan.c
#include 
#include 
#include 
#include 
#include 

void handler(int sig) {
  printf("hello sighup, pid = %d\n", getpid());
}

void print(char *name) {
  printf("%s: pid = %d, ppid = %d, pgrp = %d, tpgrp = %d\n", 
      name, getpid(), getppid(), getpgid(getpid()), tcgetpgrp(0));
  fflush(stdout);
}

int main() {
  char c;
  pid_t pid;
  print("parent");

  pid = fork();
  if (pid < 0) {
    perror("fork");
  }   
  else if (pid > 0) {
    sleep(5);
  }
  else {
    print("child");
    signal(SIGHUP, handler);
    kill(getpid(), SIGTSTP); // 让子进程暂停
    print("child"); // 如果执行了此行,说明已经收到了 SIGHUP 信号

    if (read(STDIN_FILENO, &c, 1) != 1) {
      printf("read error, error number: %d\n", errno);
    }   
    exit(0);
  }
  return 0;
}
  • 编译和运行
$ gcc orphan.c
$ ./orphan
  • 结果


72-孤儿进程与孤儿进程组_第2张图片
图2 运行结果

上面的结果正是预期的。

程序一开始让父进程打印信息,接着 fork 出子进程,让子进程打印信息,然后子进程进入停止状态。

等待父进程结束后,子进程所在的组变成了孤儿进程组,同时它也是后台进程组。接着所有子进程会收到 SIGHUP 信号,因为子进程对该信号进行了捕获,信号处理函数向控制终端输出 hello sighup, pid = 3080.

接下来子进程继续执行 print 向屏幕输出信息,注意此时的前台进程组 tpgrp = 3056,这是 bash 进程所在的进程组 id,也等于 bash 进程的 id.

最后,子进程试图读控制终端,于是收到 SIGTTIN 信号,read 返回出错,errno 被设置为 5,也就是 EIO,解释如下:

EIO I/O 错误。当进程位于后台进程组时,还试图从控制终端读,就会产生此错误。

为了验证其它未停止的进程也能收到信号,你可以多 fork 一个子进程来进行测试。

5. 总结

  • 理解什么是孤儿进程组
  • 理解进程组中存在和不存在停止的进程的影响

练习:产生一个孤儿进程组,该进程组中有停止的进程,在程序中捕获 SIGHUP 信号。

你可能感兴趣的:(linux,编程学习笔记,Linux,环境编程修炼指南-外功心法)