zoukankan      html  css  js  c++  java
  • [apue] 等待子进程的那些事儿

    前言

    谈到等待子进程,首先想到的就是 SIGCHLD 信号与 wait 函数族,本文试图厘清二者的方方面面,以及组合使用时可能的坑。

    单独使用 SIGCHLD 的场景

    使用 signal 捕获信号

    下面是一段典型的代码片段:

     1 #include "../apue.h" 
     2 #include <sys/wait.h> 
     3 
     4 #define CLD_NUM 2
     5 static void sig_cld (int signo)
     6 {
     7     pid_t pid = 0; 
     8     int status = 0; 
     9     printf ("SIGCHLD received
    "); 
    10     if (signal (SIGCHLD, sig_cld) == SIG_ERR)
    11         perror ("signal error"); 
    12     if ((pid = wait (&status)) < 0)
    13         perror ("wait(in signal) error"); 
    14     printf ("pid (wait in signal) = %d
    ", pid); 
    15 }
    16 
    17 int main ()
    18 {
    19     pid_t pid = 0; 
    20     __sighandler_t ret = signal (SIGCHLD, sig_cld);
    21     if (ret == SIG_ERR)
    22         perror ("signal error"); 
    23     else 
    24         printf ("old handler %x
    ", ret); 
    25 
    26     for (int i=0; i<CLD_NUM; ++ i)
    27     {
    28         if ((pid = fork ()) < 0)
    29             perror ("fork error"); 
    30         else if (pid == 0) 
    31         {
    32             sleep (3); 
    33             printf ("child %u exit
    ", getpid ()); 
    34             _exit (0); 
    35         }
    36 
    37         sleep (1); 
    38     }
    39 
    40     for (int i=0; i<CLD_NUM; ++ i)
    41     {
    42         pause (); 
    43         printf ("wake up by signal %d
    ", i); 
    44     }
    45 
    46     printf ("parent exit
    "); 
    47     return 0; 
    48 }

     父进程启动了两个子进程,在 SIGCHLD 信号处理器中调用 wait 等待已结束的子进程,回收进程信息,防止产生僵尸进程 (zombie)。上面的代码会有如下的输出:

    old handler 0
    child 28542 exit
    SIGCLD received
    pid (wait in signal) = 28542
    wake up by signal 0
    child 28543 exit
    SIGCLD received
    pid (wait in signal) = 28543
    wake up by signal 1
    parent exit
    

    使用 sigaction 捕获信号

    当然捕获 SIGCHLD 也可以使用 sigaction 函数:

     1 #include "../apue.h" 
     2 #include <sys/wait.h> 
     3 
     4 #define CLD_NUM 2
     5 static void sig_cld (int signo, siginfo_t *info, void* param)
     6 {
     7     int status = 0; 
     8     if (signo == SIGCHLD)
     9     {
    10         if (info->si_code == CLD_EXITED ||
    11                 info->si_code == CLD_KILLED || 
    12                 info->si_code == CLD_DUMPED)
    13         {
    14             //printf ("child %d die
    ", info->si_pid); 
    15             if (waitpid (info->si_pid, &status, 0) < 0)
    16                 perror ("wait(in signal) error"); 
    17             printf ("pid (wait in signal) = %d
    ", info->si_pid); 
    18         }
    19         else 
    20         {
    21             printf ("unknown signal code %d
    ", info->si_code); 
    22         }
    23     }
    24 }
    25 
    26 int main ()
    27 {
    28     pid_t pid = 0; 
    29     struct sigaction act; 
    30     sigemptyset (&act.sa_mask); 
    31     act.sa_sigaction = sig_cld; 
    32     act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP; 
    33     int ret = sigaction (SIGCHLD, &act, 0); 
    34     if (ret == -1)
    35         perror ("sigaction error"); 
    36 
    37     for (int i=0; i<CLD_NUM; ++ i)
    38     {
    39         if ((pid = fork ()) < 0)
    40             perror ("fork error"); 
    41         else if (pid == 0) 
    42         {
    43             sleep (3); 
    44             printf ("child %u exit
    ", getpid ()); 
    45             _exit (0); 
    46         }
    47 
    48         sleep (1); 
    49     }
    50 
    51     for (int i=0; i<CLD_NUM; ++ i)
    52     {
    53         pause (); 
    54         printf ("wake up by signal %d
    ", i); 
    55     }
    56 
    57     printf ("parent exit
    "); 
    58     return 0; 
    59 }

    输出是一样的。关于 signal 与 sigaction 的区别,有以下几点:

    • 使用 sigaction 可以避免重新安装信号处理器的问题;
    • 使用 sigaction 可以在 wait 之前得知是哪个子进程结束了。这是通过指定 SA_SIGINFO 标志位,并提供带 siginfo_t 参数的信号处理器来实现的 (info->si_pid 就是结束进程的进程号);
    • 使用 sigaction 可以获取除子进程结束以外的状态变更通知,例如挂起、继续,默认接收相应通知,除非指定 SA_NOCLDSTOP 标志。而对于 signal 而言,没有办法不接收子进程非结束状态的通知 (此时调用 wait 可能会卡死);
    • 使用 sigaction 可以自动 wait 已结束的子进程,只要指定 SA_NOCLDWAIT 标志即可。此时在信号处理器中不用再调用 wait 函数了。

    当使用 SA_NOCLDWAIT 标志位时,使用 systemtap 还是可以观察到子进程还是向父进程发送了 SIGCHLD 信号的:

    30049    cldsig           30048 cldsig           17     SIGCHLD         
    30050    cldsig           30048 cldsig           17     SIGCHLD   
    

    很有可能是系统内部自动 wait 了相关子进程。另外在使用 SA_NOCLDWAIT 时,可以不指定信号处理器,此时 sa_sigaction 字段可以设置为 SIG_DFL。关于 SIGCHLD 信号,有以下几点需要注意:

    • 如果在注册信号之前,就已经有已结束但未等待的子进程存在,则事件不会被触发;
    • 可以为 SIGCHLD 注册一个处理器,也可以忽略该信号 (SIG_IGN),忽略时系统自动回收已结束的子进程;

    当正常捕获 SIGCHLD 时,使用 systemtap 是可以观察到子进程向父进程发送的 SIGCHLD 信号的:

    29877    cldsig           29876 cldsig           17     SIGCHLD         
    29878    cldsig           29876 cldsig           17     SIGCHLD         
    29876    cldsig           27771 bash             17     SIGCHLD  
    

    当忽略 SIGCHLD 时,是观察不到的,只能看到父进程结束时向 bash 发送的 SIGCHLD 信号:

    29893    cldsig           27771 bash             17     SIGCHLD  
    

    这里注意一下二者在细节处的一点区别。

    •  还有一个 SIGCLD 信号 (看清楚,只差了一个字母),在大多数 unix like 系统中与 SIGCHLD 表现一致,在某些古老的 unix 系统上,可能有独特的表现需要注意,这方面请参考 apue 第十章第七节

    在我的环境 (CentOS 6.7) 该信号被定义为 SIGCHLD,因此是完全等价的。

    屏蔽信号

    关于使用信号等待子进程,最后需要说的一点就是信号的竞争行为,对上面的例子稍加修改,就可以演示一下:

     1 #include "../apue.h" 
     2 #include <sys/wait.h> 
     3 
     4 #define CLD_NUM 2
     5 void pid_remove (pid_t pid)
     6 {
     7     printf ("remove pid %u
    ", pid); 
     8 }
     9 void pid_add (pid_t pid)
    10 {
    11     printf ("add pid %u
    ", pid); 
    12 }
    13 
    14 static void sig_cld (int signo)
    15 {
    16     pid_t pid = 0; 
    17     int status = 0; 
    18     printf ("SIGCHLD received
    "); 
    19     if (signal (SIGCHLD, sig_cld) == SIG_ERR)
    20         perror ("signal error"); 
    21     if ((pid = wait (&status)) < 0)
    22         perror ("wait(in signal) error"); 
    23     printf ("pid (wait in signal) = %d
    ", pid); 
    24     pid_remove (pid); 
    25 }
    26 
    27 int main ()
    28 {
    29     pid_t pid = 0; 
    30     __sighandler_t ret = signal (SIGCHLD, sig_cld);
    31     if (ret == SIG_ERR)
    32         perror ("signal error"); 
    33     else 
    34         printf ("old handler %x
    ", ret); 
    35 
    36     for (int i=0; i<CLD_NUM; ++ i)
    37     {
    38         if ((pid = fork ()) < 0)
    39             perror ("fork error"); 
    40         else if (pid == 0) 
    41         {
    42             //sleep (3); 
    43             printf ("child %u exit
    ", getpid ()); 
    44             _exit (0); 
    45         }
    46 
    47         sleep (1);
    48         pid_add (pid);  
    49     }
    50 
    51     sleep (1); 
    52     printf ("parent exit
    "); 
    53     return 0; 
    54 }

    父进程在启动子进程后需要将它的信息通过 pid_add 添加到某种数据结构中,当收到 SIGCHLD 信号后,又通过 pid_remove 将它从这个数据结构中移出。在上面的例子中,子进程一启动就退出了,快到甚至父进程还没有来得及执行 pid_add 就先执行了 pid_remove,这很容易导致潜在的问题。(注意,为了能更好的呈现信号竞争的问题,这里故意在父进程 sleep 之后调用 pid_add),执行结果如下:

    old handler 0
    child 31213 exit
    SIGCLD received
    pid (wait in signal) = 31213
    remove pid 31213
    add pid 31213
    child 31214 exit
    SIGCLD received
    pid (wait in signal) = 31214
    remove pid 31214
    add pid 31214
    parent exit
    

    可以看到,remove 总是在 add 之前执行。而解决方案也很直接,就是在 pid_add 完成之前,我们需要屏蔽 SIGCHLD 信号:

     1     for (int i=0; i<CLD_NUM; ++ i)
     2     {
     3         sigset_t mask; 
     4         sigemptyset(&mask);
     5         sigaddset(&mask, SIGCHLD);
     6         sigprocmask(SIG_BLOCK, &mask, NULL);
     7         if ((pid = fork ()) < 0)
     8             perror ("fork error"); 
     9         else if (pid == 0) 
    10         {
    11             sigprocmask(SIG_UNBLOCK, &mask, NULL);
    12             //sleep (3); 
    13             printf ("child %u exit
    ", getpid ()); 
    14             _exit (0); 
    15         }
    16 
    17         sleep (1);
    18         pid_add (pid);  
    19         sigprocmask(SIG_UNBLOCK, &mask, NULL);
    20     }

    这里用到了 sigprocmask 去屏蔽以及解除某种信号的屏蔽。新的代码运行结果如下:

    old handler 0
    child 31246 exit
    add pid 31246
    SIGCLD received
    pid (wait in signal) = 31246
    remove pid 31246
    child 31247 exit
    SIGCLD received
    pid (wait in signal) = 31247
    remove pid 31247
    add pid 31247
    parent exit
    

    可以看到一切正常了,add 这次位于 remove 之前。总结一下,使用 SIGCHLD 信号适合异步等待子进程的场景,并且通常搭配 wait 来回收子进程。

    单独使用 wait 函数族的场景

    典型代码如下:

     1 #include "../apue.h" 
     2 #include <sys/wait.h> 
     3 
     4 #define CLD_NUM 2
     5 int main ()
     6 {
     7     pid_t pid = 0; 
     8     for (int i=0; i<CLD_NUM; ++ i)
     9     {
    10         if ((pid = fork ()) < 0)
    11             perror ("fork error"); 
    12         else if (pid == 0) 
    13         {
    14             sleep (3); 
    15             printf ("child %u exit
    ", getpid ()); 
    16             _exit (0); 
    17         }
    18 
    19         sleep (1); 
    20     }
    21 
    22     int status = 0; 
    23     for (int i=0; i<CLD_NUM; ++ i)
    24     {
    25         if ((pid = wait (&status)) < 0)
    26             perror ("wait error"); 
    27 
    28         printf ("pid = %d
    ", pid); 
    29     }
    30 
    31     printf ("parent exit
    "); 
    32     return 0; 
    33 }

    与之前场景不同的是,这里父进程同步等待启动的子进程结束。上面的代码会有如下输出:

    child 28583 exit
    child 28584 exit
    pid = 28583
    pid = 28584
    parent exit
    

    关于wait函数族,需要注意以下几点:

    • wait 用于等待任何一个子进程,相当于 waitpid (-1, status, 0);  当没有任何子进程存在时,返回 -1,errno 设置为 ECHILD;
    • waitpid 相对于 wait 的优势在于:
      • 可以指定子进程 (组) 来等待;
      • 可以捕获子进程除结束以外的其它状态变更通知,如挂起 (WUNTRACED)、继续 (WCONTINUED) 等;
      • 可以不阻塞的测试某个子进程是否已结束 (WNOHANG);
    • wait 函数族可被信号中断,此时返回 -1,errno 设置为 EINTR,必要时需要重启 wait;

    总结一下,使用 wait 函数族适合同步等待子进程,例如某种命令执行器进程,通常配合 waitpid 来回收子进程。

    混合使用同步 wait 与异步 wait 函数族的场景

    其实前面已经提到 SIGCHLD 要搭配 wait 使用,但那是异步使用 wait 的单一场景,而这里讲的混合,是指同时在信号处理器与执行流程中使用 wait。例如 bash,它除了在主线程中同步等待前台正在运行的子进程,还必需在信号处理器中异步接收后台运行子进程的状态反馈,这样就不得不混合使用 wait。同步等待某个子进程一般使用 waitpid,而在信号处理器中一般使用 wait,典型的代码如下所示:

     1 #include "../apue.h" 
     2 #include <sys/wait.h> 
     3 #include <errno.h> 
     4 
     5 #define CLD_NUM 2
     6 
     7 static void sig_cld (int signo)
     8 {
     9     pid_t pid = 0; 
    10     int status = 0; 
    11     printf ("SIGCLD received
    "); 
    12     if (signal (SIGCLD, sig_cld) == SIG_ERR)
    13         perror ("signal error"); 
    14 
    15     if ((pid = wait (&status)) < 0)
    16         perror ("wait(in signal) error"); 
    17     else
    18         printf ("pid (wait in signal) = %d
    ", pid); 
    19 }
    20 
    21 int main ()
    22 {
    23     pid_t pid = 0; 
    24     __sighandler_t ret = signal (SIGCLD, sig_cld);
    25     if (ret == SIG_ERR)
    26         perror ("signal error"); 
    27     else 
    28         printf ("old handler %x
    ", ret); 
    29 
    30     for (int i=0; i<CLD_NUM; ++ i)
    31     {
    32         if ((pid = fork ()) < 0)
    33             perror ("fork error"); 
    34         else if (pid == 0) 
    35         {
    36             if (i % 2 == 0) { 
    37                 // simulate background
    38                 sleep (3); 
    39             }
    40             else {
    41                 // simulate foreground
    42                 sleep (4); 
    43             }
    44 
    45             printf ("child %u exit
    ", getpid ()); 
    46             _exit (0); 
    47         }
    48 
    49         sleep (1); 
    50     }
    51 
    52     int status = 0; 
    53     printf ("before wait pid %u
    ", pid); 
    54     if (waitpid (pid, &status, 0) < 0)
    55         printf ("wait %u error %d
    ", pid, errno); 
    56     else
    57         printf ("wait child pid = %d
    ", pid); 
    58 
    59     sleep (2);
    60     printf ("parent exit
    "); 
    61     return 0; 
    62 }

    父进程启动两个子进程,第一个休眠 3 秒后退出,第二个休眠 4 秒后退出,由于父进程同步等待的是第二个子进程,因此第二个进程模拟前台进程,第一个进程模拟后台进程。运行输出如下:

    old handler 0
    before wait pid 2481
    child 2480 exit
    SIGCLD received
    pid (wait in signal) = 2480
    wait 2481 error 4
    child 2481 exit
    SIGCLD received
    pid (wait in signal) = 2481
    parent exit
    

    此时同步等待的 waitpid 被信号中断了 (EINTR),此种情况下,我们需要重启 waitpid:

     1     int status = 0; 
     2     while (1) { 
     3         printf ("before wait pid %u
    ", pid); 
     4         if (waitpid (pid, &status, 0) < 0)
     5         {
     6             int err = errno; 
     7             printf ("wait %u error %d
    ", pid, err); 
     8             if (err == EINTR)
     9                 continue; 
    10         }
    11         else
    12             printf ("wait child pid = %d
    ", pid); 
    13 
    14         break; 
    15     }

    新的代码输出如下:

    old handler 0
    before wait pid 2513
    child 2512 exit
    SIGCLD received
    pid (wait in signal) = 2512
    wait 2513 error 4
    before wait pid 2513
    child 2513 exit
    SIGCLD received
    wait(in signal) error: No child processes
    wait child pid = 2513
    parent exit
    

    可以看到两个进程退出时,都收到了 SIGCHLD 信号,只是前台进程被 waitpid 优先等待到了,所以信号处理器中的 wait 返回的 ECHILD 错误。但是如果还有其它子进程在运行,信号处理器里的 wait 会卡死。

    忽略信号

    之前提到,可以使用 SIG_IGN 来自动回收子进程,这里试一下使用 SIG_IGN 来代替 sig_cld,看看有什么改观:

    old handler 0
    before wait pid 2557
    child 2556 exit
    child 2557 exit
    wait 2557 error 10
    parent exit
    

    同样的,两个子进程都走了忽略信号,而同步等待的 waitpid 因没有进程可等返回了 ECHILD。因为 waitpid 是指定进程等待的,所以即使还有其它子进程存在,这个也会返回错误,不会卡死在那里。相比上面的方法,似乎好了一点,但是因为我们没有安装处理器,所以无从得知哪个后台进程结束了,这并不是我们想到的结果。

    使用 sigaction

    之前提到,可以使用 sigaction 代替 signal 以获取更多的控制,我们看看换新的方式捕获信号,会不会有一些改变,新的代码逻辑如下:

     1 #include "../apue.h" 
     2 #include <sys/wait.h> 
     3 #include <errno.h> 
     4 
     5 #define CLD_NUM 2
     6 
     7 static void sig_cld (int signo, siginfo_t *info, void* param)
     8 {
     9     int status = 0; 
    10     if (signo == SIGCHLD)
    11     {
    12         if (info->si_code == CLD_EXITED ||
    13                 info->si_code == CLD_KILLED || 
    14                 info->si_code == CLD_DUMPED)
    15         {
    16             if (waitpid (info->si_pid, &status, 0) < 0)
    17                 err_ret ("wait(in signal) %u error", info->si_pid); 
    18             else 
    19                 printf ("pid (wait in signal) = %d
    ", info->si_pid); 
    20         }
    21         else 
    22         {
    23             printf ("unknown signal code %d
    ", info->si_code); 
    24         }
    25     }
    26 }
    27 
    28 int main ()
    29 {
    30     pid_t pid = 0; 
    31     struct sigaction act; 
    32     sigemptyset (&act.sa_mask); 
    33     act.sa_sigaction = sig_cld; 
    34     act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP; 
    35     int ret = sigaction (SIGCHLD, &act, 0); 
    36     if (ret == -1)
    37         perror ("sigaction error"); 
    38 
    39     for (int i=0; i<CLD_NUM; ++ i)
    40     {
    41         if ((pid = fork ()) < 0)
    42             perror ("fork error"); 
    43         else if (pid == 0) 
    44         {
    45             if (i % 2 == 0) { 
    46                 // simulate background
    47                 sleep (3); 
    48             }
    49             else {
    50                 // simulate foreground
    51                 sleep (4); 
    52             }
    53 
    54             printf ("child %u exit
    ", getpid ()); 
    55             _exit (0); 
    56         }
    57 
    58         sleep (1); 
    59     }
    60 
    61     int status = 0; 
    62     while (1) { 
    63         printf ("before wait pid %u
    ", pid); 
    64         if (waitpid (pid, &status, 0) < 0)
    65         {
    66             int err = errno; 
    67             printf ("wait %u error %d
    ", pid, err); 
    68             if (err == EINTR)
    69                 continue; 
    70         }
    71         else
    72             printf ("wait child pid = %d
    ", pid); 
    73 
    74         break; 
    75     }
    76 
    77     sleep (2);
    78     printf ("parent exit
    "); 
    79     return 0; 
    80 }

    运行输出如下:

    before wait pid 2585
    child 2584 exit
    pid (wait in signal) = 2584
    wait 2585 error 4
    before wait pid 2585
    child 2585 exit
    wait(in signal) 2585 error: No child processes
    wait child pid = 2585
    parent exit
    

    结果与使用 signal 很相似,但是因为在信号处理器中我们能明确的知道是哪个子进程终结了,使用的是 waitpid 而不是 wait,所以即使还有其它子进程在运行,也不会在信号处理器的 waitpid 中卡住。因此结论是无论使用 signal 还是 sigaction,同步等待的 waitpid 总比 SIGCHLD 信号处理器中的 wait(xxx) 具有更高的优先级。

    当然,这个前提是在父进程同步 waitpid 之前子进程还没有结束;如果要等待的子进程先结束了,SIGCHLD 当然先被执行,这种情况下,建议先使用 sigprocmask 屏蔽 SIGCHLD 信号,然后在 waitpid 之前解除屏蔽。虽然不能保证完全解决信号竞争的问题,也能极大的缓解此种情况。退一步讲,假如出现了信号竞争导致同步等待的 waitpid 返回 ECHILD,我们也能从这些错误码中得知发生的事情,不会出现卡死的情况。出于好奇,我们看一下改使用 SIG_IGN 后的运行效果:

    before wait pid 2613
    child 2612 exit
    child 2613 exit
    wait 2613 error 10
    parent exit
    

    与使用 signal 时并无二致,仍然是忽略信号占了上风。因此结论是无论使用 signal 还是 sigaction,当忽略 SIGCHLD 信号时,信号优先于 wait 被忽略。出于同样的原因,这种方式我们并不采纳。

    使用 SA_NOCLDWAIT

    之前提到,sigaction还有一种高级的忽略 SIGCHLD 的方式,即指定 SA_NOCLDWAIT 标志位,同时给信号处理器指定 SIG_DFL,这种情况下,我们看看输出会有什么变化:

    before wait pid 2719
    child 2718 exit
    child 2719 exit
    wait 2719 error 10
    parent exit
    

    可以看到,与使用 SIG_IGN 并无二致。我们可以为 SIGCHLD 提供一个处理器,虽然在此信号处理器中无需再次等待子进程,但是我们拥有了获取子进程信息的能力,相对而言,比 SIG_IGN 更有用一些。新的输出如下:

    before wait pid 2737
    child 2736 exit
    pid (auto wait in signal) = 2736
    wait 2737 error 4
    before wait pid 2737
    child 2737 exit
    pid (auto wait in signal) = 2737
    wait 2737 error 10
    parent exit
    

    可以看到,同步 waitpid 仍然返回 ECHILD,显然是信号更具有优先级。好了,事情至此就全明了了,对于混合使用同步与异步 wait 的应用来说,最佳的方法应该是同步 waitpid 等待前台进程,使用sigaction 注册 SIGCHLD 信号处理器异步等待后台进程,且不设置 SA_NOCLDWAIT 标志位。在处理器中也应使用 waitpid 等待子进程,如返回 ECHILD 错误,证明该子进程是前台进程,已经被同步 wait 掉了,不需要任何处理;否则作为后台进程处理。

    后记

    说了这么一大堆,可能有的人会说了,我又不需要写一个 shell,需要用到这么复杂的知识吗? 确实,没有多少人会有机会写一个 shell,但是并非只有 shell 才有混合使用同步、异步等待子进程的场景,考虑下面这个场景:

     1 #include "../apue.h" 
     2 #include <unistd.h> 
     3 #include <sys/wait.h> 
     4 
     5 #define PAGER "${PAGER:-more}"
     6 
     7 #define USE_SIG 2
     8 static void sig_cld (int signo)
     9 {
    10     pid_t pid = 0; 
    11     int status = 0; 
    12     printf ("SIGCLD received
    "); 
    13     if (signal (SIGCLD, sig_cld) == SIG_ERR)
    14         perror ("signal error"); 
    15 
    16     if ((pid = wait (&status)) < 0)
    17         perror ("wait(in signal) error"); 
    18 
    19     printf ("pid (wait in signal) = %d
    ", pid); 
    20 }
    21 
    22 void install_handler (__sighandler_t h)
    23 {
    24     __sighandler_t ret = signal (SIGCLD, h);
    25     if (ret == SIG_ERR)
    26         perror ("signal error"); 
    27     else 
    28         printf ("old handler %x
    ", ret); 
    29 }
    30 
    31 int main (int argc, char *argv[])
    32 {
    33     int n = 0; 
    34 #if USE_SIG == 1
    35     install_handler (sig_cld); 
    36 #elif USE_SIG == 2
    37     install_handler (SIG_IGN); 
    38 #endif
    39 
    40     char line[MAXLINE] = { 0 }; 
    41     FILE *fpin = NULL, *fpout = NULL; 
    42     if (argc != 2)
    43         err_quit ("usage: ppage <pathname>"); 
    44 
    45     fpin = fopen (argv[1], "r"); 
    46     if (fpin == NULL)
    47         err_sys ("can't open %s", argv[1]); 
    48 
    49     fpout = popen (PAGER, "w"); 
    50     if (fpout == NULL)
    51         err_sys ("popen %s error", PAGER); 
    52 
    53     while (fgets (line, MAXLINE, fpin) != NULL) { 
    54         if (fputs (line, fpout) == EOF)
    55             err_sys ("fputs error to pipe"); 
    56     }
    57 
    58     if (ferror (fpin))
    59         err_sys ("fgets error"); 
    60 
    61     int ret = pclose(fpout); 
    62     if (ret == -1)
    63         err_sys ("pclose error"); 
    64     else 
    65         printf ("worker return %d
    ", ret); 
    66 
    67     return 0; 
    68 }

    程序运行后打开参数指定的文件,读取并将它通过管道传递给 more 命令。随后通过 pclose 等待 more 命令结束。这期间为了保证其它子进程 (假设存在) 能正常回收,使用 SIG_IGN 注册了 SIGCHLD 信号。运行程序,退出 more 后有如下输出:

    pclose error: No child processes
    

    pclose 失败了,为什么呢?答案就是前面说过的,pclose 内部存在着一个隐式的 waitpid 在同步等待 more 子进程,而此时 SIGCHLD 被注册为忽略取得了优先权,导致 waitpid 失败从而导致 pclose 返回错误。可见,当程序中存在 pclose、system 等隐式 wait 调用时,如果同时需要 SIGCHLD 信号处理,则一定不能:

    • 注册为忽略 SIG_IGN;
    • 通过 sigaction 注册并设置 SA_NOCLDWAIT 标志位;

    否则相应的调用会失败。顺便说一下,之前发现同步等待的 waitpid 没有被中断的情况只在忽略信号的时候产生,而前面也证明了忽略信号时,系统压根不产生 SIGCHLD 信号,这两者似乎到现在是对上了……

    下载

    场景 1&2 测试代码

    场景3 测试代码

    使用popen的场景

    完整的shell示例

  • 相关阅读:
    Sql Server 2016 创建内存数据库
    SQL 系统表应用
    EF 编程经验
    JSP JDBC 读取SQL Server 数据2
    JSP 使用 JDBC连接SQL Server
    JSP 用poi 读取Excel
    解析SQL中的包含的列和表
    dotnet core 入门
    <<你的灯亮着吗?>>读书笔记
    需求分析过程工具产物表
  • 原文地址:https://www.cnblogs.com/goodcitizen/p/things_about_waiting_child_process.html
Copyright © 2011-2022 走看看