在这种情况下如何处理SIGCHILD信号?

时间:2017-04-16 17:59:15

标签: c shell unix operating-system

我想了解这段代码,

int count =0;
void handler(int sig){
    count++;
}

int main(){
    signal(SIGCHILD,handler);
    for(int i=0;i<4;i++){
        if(!fork()){
            exit(0 );
        }
    }

    while(wait(NULL) ! = -1){

    }

    print(count)
}

所以,我希望因为有4个SIGCHILD信号,所以处理器应该被调用四次。但是,由于我们可以有一个最大的待处理信号,因此可能会丢弃一些信号,而计数可能不会是四个。

但是,如果父进程在单个子进程退出之前调用wait,那么SIGCHILD信号将如何处理?在这种情况下,计数是4吗?

在这种情况下,SIGCHILD,处理程序代码和父进程等待之间的流程如何?

1 个答案:

答案 0 :(得分:0)

有点清理的代码版本变成了MCVE(Minimal, Complete, Verifiable Example):

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

static volatile sig_atomic_t count = 0;

static void handler(int sig)
{
    assert(sig == SIGCHLD);
    count++;
}

int main(void)
{
    signal(SIGCHLD, handler);
    for (int i = 0; i < 4; i++)
    {
        if (fork() == 0)
        {
            exit(16 * (i + 1));
        }
    }

    int corpse;
    int status;
    while ((corpse = wait(&status)) != -1 || errno == EINTR)
    {
        if (corpse == -1 && errno == EINTR)
            printf("Interrupted by a signal\n");
        else
            printf("Child %d exited with status 0x%.4X\n", corpse, status);
    }

    printf("Count = %d\n", count);
    return 0;
}

在运行带有GCC 6.3.0的macOS Sierra 10.12.4的Mac上运行时,我得到了以下主题的变体:

Child 74003 exited with status 0x1000
Child 74004 exited with status 0x2000
Child 74005 exited with status 0x3000
Child 74006 exited with status 0x4000
Count = 4

在这台机器上(一台现代化的15“2016 MacBook Pro),我似乎总是将它作为输出 - 依次为孩子们处理ID,并精心设计退出状态。

当我更改这样的处理程序时(考虑到how to avoid calling printf() in a signal handler的限制 - 是的,我知道我可以键入STDIN_FILENO而不是其中一些1 s:< / p>

static void handler(int sig)
{
    assert(sig == SIGCHLD);
    count++;
    char s[2] = { count + '0', '\n' };
    write(1, "SH: count = ", sizeof("SH: count = ")-1);
    write(1, s, 2);
}

然后输出更改为更像这样的东西:

SH: count = 1
SH: count = 2
Child 74113 exited with status 0x1000
Child 74114 exited with status 0x2000
SH: count = 3
Child 74115 exited with status 0x3000
SH: count = 4
Child 74116 exited with status 0x4000
Count = 4

这表明信号处理程序在循环期间的不同时间被调用。 BSD信号处理程序(以及macOS或Darwin在某种程度上基于BSD)倾向于重新启动系统调用而不是中断。因此,我看到的并不一定是您在不同平台上看到的内容。

例如,在Ubuntu 16.04 LTS VM中运行,我得到了输出:

SH: count = 1
Child 13310 exited with status 0x4000
Child 13309 exited with status 0x3000
Child 13308 exited with status 0x2000
Child 13307 exited with status 0x1000
Count = 1

但是,使用信号处理程序的另一个修改 - 重置信号处理程序中的信号处理程序,因为signal()设置的处理程序的传统(非BSD)行为是默认值在处理程序之前重置调用函数,如果忽略write()的返回值,则检查自Linux发出警告以来写入的字节数:

static void handler(int sig)
{
    //assert(sig == SIGCHLD);
    signal(sig, handler);
    count++;
    char s[2] = { count + '0', '\n' };
    int nb = write(1, "SH: count = ", sizeof("SH: count = ")-1);
    assert(nb == sizeof("SH: count = ")-1);
    nb = write(1, s, 2);
    assert(nb == 2);
}

然后输出变为:

SH: count = 1
Child 13838 exited with status 0x4000
SH: count = 2
Child 13837 exited with status 0x3000
SH: count = 3
Child 13836 exited with status 0x2000
SH: count = 4
Child 13835 exited with status 0x1000
Count = 4

因此,正如您所看到的,您看到的结果取决于运行代码的平台,以及handler()函数的确切编写方式。