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

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

    1. 首先谈单独使用SIGCHLD的场景。下面是一段典型的代码片段:

     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
    

    当然捕获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的区别,有以下几点:

    a) 使用sigaction可以避免重新安装信号处理器的问题;

    b) 使用sigaction可以在wait之前得知是哪个子进程结束了,这是通过指定SA_SIGINFO标志位,并提供带siginfo_t参数的信号处理器来实现的(info->si_pid就是结束的进程号);

    c) 使用sigaction可以获取除子进程结束以外的状态变更通知,例如挂起、继续,默认接收相应通知,除非指定SA_NOCLDSTOP标志。而对于signal而言,没有办法不接收子进程非结束状态的通知(此时调用wait可能会卡死);

    d) 使用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信号,有以下几点需要注意:

    a) 如果在注册信号之前,就已经有已结束但未等待的子进程存在,则事件不会被触发;

    b) 可以为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  
    

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

     c) 还有一个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来回收子进程。

    2. 然后谈单独使用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函数族,需要注意以下几点:

    a) wait用于等待任何一个子进程,相当于waitpid(-1, status, 0); 当没有任何子进程存在时,返回-1,errno设置为ECHILD;

    b) waitpid相对于wait的优势在于:

      i) 可以指定子进程(组)来等待;

      ii) 可以捕获子进程除结束以外的其它状态变更通知,如挂起(WUNTRACED)、继续(WCONTINUED)等;

      iii) 可以不阻塞的测试某个子进程是否已结束(WNOHANG);

    c) wait函数族可被信号中断,此时返回-1,errno设置为EINTR,必要时需要重启wait;

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

    3. 最后谈谈混合使用同步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     }

    如果因EINTR引发的错误,则重新调用waitpid;否则,退出。新的代码输出如下:

    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代替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被忽略。

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

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

     可以看到,与使用SIG_IGN并无二致。

    与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才有混合使用同步、异步等待子进程的场景,考虑下面个场景:

     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信号处理,则一定不能 (a)注册为忽略SIG_IGN (b)通过sigaction注册并设置SA_NOCLDWAIT标志位,否则相应的调用会失败。

     最后补充一点,我们发现同步等待的waitpid没有被中断的情况只在忽略信号的时候产生,而之前也证明了忽略信号时,系统压根不产生SIGCHLD信号,这两者似乎到现在是对上了…… :)

    场景 1&2 测试代码

    场景3 测试代码

    使用popen的场景

    完整的shell示例

  • 相关阅读:
    Halcon学习笔记之缺陷检测(二)
    tensorflow常用函数库
    luogu P2765 魔术球问题 (最小路径覆盖)
    luogu P2423 [HEOI2012]朋友圈 (最大团)
    poj 2226 Muddy Fields (二分图)
    匈牙利算法板子
    二分图
    AC日记——【模板】树链剖分 洛谷 P3384
    AC日记——[ZJOI2008]树的统计Count bzoj 1036
    去重排序
  • 原文地址:https://www.cnblogs.com/goodcitizen/p/11133705.html
Copyright © 2011-2022 走看看