2

我正在尝试了解阻塞和解除阻塞信号的工作原理,并且我正在尝试理解以下代码。具体来说,我正在查看第 28 行(在代码中注释)int a = sigprocmask(SIG_UNBLOCK, &mask, NULL);:,也就是信号在孩子中畅通的地方。

我从中得到代码的教科书说代码使用信号阻塞来确保程序printf("adding %d\n", pid);在其删除功能(简化为)之前执行其添加功能(简化为printf("deleting %d\n", pid);)。这对我来说很有意义;通过阻塞SIGCHLD信号,然后在执行 add 函数后解除阻塞,我们确保在执行 add 函数之前不会调用处理程序。但是,我们为什么要解锁孩子的信号呢?这不是通过立即解除阻塞来消除阻塞的全部意义,允许孩子在父添加之前删除吗?

但是,无论我是否注释掉了该行,输出(在代码之后描述)都是相同的,这意味着这显然不是发生的事情。教科书说:

SIGCHLD“注意孩子继承了父母的屏蔽集,所以在调用之前我们必须小心解锁孩子的信号execve。”

但在我看来,解除阻塞仍会导致调用处理程序。这条线具体是做什么的?

void handler(int sig) {
    pid_t pid;
    printf("here\n");
    while ((pid = waitpid(-1, NULL, 0)) > 0); /* Reap a zombie child */
    printf("deleting %d\n", pid); /* Delete the child from the job list */
}

int main(int argc, char **argv) {
    int pid;
    sigset_t mask;
    signal(SIGCHLD, handler);
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, NULL); /* Block SIGCHLD */

    pid = fork();
    if (pid == 0) {
        printf("in child\n");

        int a = sigprocmask(SIG_UNBLOCK, &mask, NULL); // LINE 28

        printf("a is %d\n",a);
        execve("/bin/date", argv, NULL);
        exit(0);
    }

    printf("adding %d\n", pid);/* Add the child to the job list */
    sleep(5);
    printf("awake\n");

    int b = sigprocmask(SIG_UNBLOCK, &mask, NULL);
    printf("b is %d\n", b);
    sleep(3);

    exit(0);
}

输出:

adding 652

in child

a is 0

Wed Apr 24 20:18:04 UTC 2019

awake

here

deleting -1

b is 0
4

1 回答 1

1

但是,我们为什么要解锁孩子的信号呢?这不是通过立即解除阻塞来消除阻塞的全部意义,允许孩子在父添加之前删除吗?

不,每个进程都有自己的信号掩码。一个新进程继承了其父进程的信号掩码,但只是在它继承父进程内存内容的同一意义上——子进程获得了相当于一个独立副本的东西。它对该副本的修改不会反映在父副本中,反之亦然。如果不是这种情况,那么系统中的所有进程将共享一个信号掩码。

只有父母不能SIGCLD太早收到,所以只有父母需要阻止那个信号。

[...]教科书指出:

“注意孩子继承了他们父母的阻塞集,所以我们必须小心在调用 execve 之前解除孩子中的 SIGCHLD 信号。”

但在我看来,解除阻塞仍会导致调用处理程序。

同样,“继承”是指继承一个副本,而不是共享同一个掩码。

这条线具体是做什么的?

它在子进程中解除SIGCLD阻塞——同样,对父进程没有影响——以防它被阻塞会干扰/bin/date子进程即将执行的行为。

于 2019-04-24T21:50:13.510 回答