【问题标题】:Will wait and waitpid block SIGCHLD and unblock it when they return in Linux?wait 和 waitpid 在 Linux 中返回时会阻塞 SIGCHLD 并解除阻塞吗?
【发布时间】:2017-10-12 10:43:52
【问题描述】:

这是我检查这个的代码:

void handler(int n) {
    printf("handler %d\n", n);
    int status;
    if (wait(&status) < 0)
        printf("%s\n", strerror(errno));
}

int main() {
    struct sigaction sig;
    sigemptyset(&sig.sa_mask);
    sig.sa_handler = handler;
    sig.sa_flags = 0;
    sig.sa_restorer = NULL;
    struct sigaction sigold;
    sigaction(SIGCHLD, &sig, &sigold);
    pid_t pid;
    int status;
    printf("before fork\n");
    if ((pid = fork()) == 0) {
        _exit(127);
    } else if (pid > 0) {
        printf("before waitpid\n");
        if (waitpid(pid, &status, 0) < 0)
            printf("%s\n", strerror(errno));
        printf("after waitpid\n");
    }
    printf("after fork\n");
    return 0;
}

输出是:

分叉前

waitpid 之前

处理程序 17

没有子进程

waitpid 之后

分叉后

所以,我认为 waitpid 会阻塞 SIGCHLD 并等待子进程终止,一旦子进程终止,它会做一些事情并在 SIGCHLD 返回之前解除阻塞,这就是为什么我们看到 “没有子进程” 错误并且 "after waitpid""handler 17" 之后,对吗?如果不是,真相是什么?如何解释输出序列?是否有 Linux 规范或类似的规范可供检查?

【问题讨论】:

标签: c linux process signals system-calls


【解决方案1】:

进程的退出信息只能收集一次。您的输出显示当您的代码位于 waitpid() 时调用了信号处理程序,但处理程序调用了 wait() 并收集了孩子的信息(您在不报告的情况下将其丢弃)。然后当您返回waitpid() 时,子退出状态已被收集,因此waitpid() 没有任何内容可报告,因此出现“无子进程”错误。

这是对您的程序的改编。它通过在信号处理函数中使用printf() 来滥用东西,但尽管如此,在运行 macOS Sierra 10.12.4(使用 GCC 7.1.0 编译)的 Mac 上测试它似乎仍然有效。

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

static void handler(int n)
{
    printf("handler %d\n", n);
    int status;
    int corpse;
    if ((corpse = wait(&status)) < 0)
        printf("%s: %s\n", __func__, strerror(errno));
    else
        printf("%s: child %d exited with status 0x%.4X\n", __func__, corpse, status);
}

int main(void)
{
    struct sigaction sig = { 0 };
    sigemptyset(&sig.sa_mask);
    sig.sa_handler = handler;
    sig.sa_flags = 0;
    sigaction(SIGCHLD, &sig, NULL);
    pid_t pid;
    printf("before fork\n");
    if ((pid = fork()) == 0)
    {
        _exit(127);
    }
    else if (pid > 0)
    {
        printf("before waitpid\n");
        int status;
        int corpse;
        while ((corpse = waitpid(pid, &status, 0)) > 0 || errno == EINTR)
        {
            if (corpse < 0)
                printf("loop: %s\n", strerror(errno));
            else
                printf("%s: child %d exited with status 0x%.4X\n", __func__, corpse, status);
        }
        if (corpse < 0)
            printf("%s: %s\n", __func__, strerror(errno));
        printf("after waitpid loop\n");
    }
    printf("after fork\n");
    return 0;
}

样本输出:

before fork
before waitpid
handler 20
handler: child 29481 exited with status 0x7F00
loop: Interrupted system call
main: No child processes
after waitpid loop
after fork

状态值 0x7F00 是_exit(127) 的正常编码。 macOS 与 Linux 的信号编号不同;这是完全允许的。


要让代码在 Linux(用于测试的 Centos 7 和 Ubuntu 16.04 LTS)上编译,分别使用 GCC 4.8.5(几乎是上古时代——当前版本是 GCC 7.1.0)和 5.4.0,使用命令行:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes -Wold-style-definition sg59.c -o sg59
$

我在第一个标题之前添加了#define _XOPEN_SOURCE 800,并使用了:

struct sigaction sig;
memset(&sig, '\0', sizeof(sig));

用 GCC 4.8.5 初始化结构。这种恶作剧有时是避免编译器警告的痛苦必要条件。我注意到虽然 #define 是公开 POSIX 符号所必需的,但初始化程序 (struct sigaction sig = { 0 };) 已被 GCC 5.4.0 接受而没有问题。

当我运行程序时,我得到的输出与cong 报告的comment 中的输出非常相似:

before fork
before waitpid
handler 17
handler: No child processes
main: child 101681 exited with status 0x7F00
main: No child processes
after waitpid loop
after fork

确实很奇怪,在 Linux 上,进程被发送了一个 SIGCHLD 信号,而wait() 却不能在信号处理程序中等待它。这至少是违反直觉的。

我们可以讨论waitpid() 的第一个参数是pid 而不是0 的重要性;自从第一次从孩子那里收集信息以来,该错误在循环的第二次迭代中是不可避免的。在实践中,这并不重要。一般来说,最好使用waitpid(0, &amp;status, WNOHANG) 或类似的位置——根据上下文,0 而不是WNOHANG 可能会更好。

【讨论】:

  • 我的平台是“gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4”,我在我的电脑上运行你的代码,输出是:fork before waitpid handler 17 handler :没有子进程主进程:子进程 4479 退出,状态为 0x7F00 主进程:fork 后 waitpid 循环后没有子进程
  • 奇怪的是,你得到一个 SIGCHLD 信号,但在处理程序中调用 wait() 并没有收获孩子。确实看起来很奇怪。我已经在 Centos 7 和 Ubuntu 16.04 LTS 上重现了你的结果——注意了解决旧编译器 (GCC 4.x) 所需的代码更改。
  • 这就是为什么我认为waitpid会先阻塞SIGCHLD并在waitpid即将返回时解除阻塞的原因,有意义吗?
  • 您可以证明这一说法的唯一方法是查看实现waitpid() 的源代码——这可能意味着用户代码或内核代码或两者兼而有之。我认为这不太可能。
  • 那么,有没有其他方法可以知道真相?系统调用源码对于我这个级别的人来说太难读了。
猜你喜欢
  • 2012-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-10
  • 2015-04-30
  • 2020-10-09
相关资源
最近更新 更多