进程的退出信息只能收集一次。您的输出显示当您的代码位于 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, &status, WNOHANG) 或类似的位置——根据上下文,0 而不是WNOHANG 可能会更好。