【发布时间】:2020-09-26 20:59:23
【问题描述】:
考虑以下用于 POSIX 系统的 C 代码:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#define CONTINUE_SIGNAL SIGINT
void continue_handler(int sig, siginfo_t *info, void *context)
{
printf("[P] Continuing process with pid = %d...\n", getpid());
}
int main(void)
{
struct sigaction act;
sigset_t mask;
pid_t pid;
// Block the CONTINUE_SIGNAL for now.
sigemptyset(&mask);
sigaddset(&mask, CONTINUE_SIGNAL);
sigprocmask(SIG_BLOCK, &mask, NULL);
if ((pid = fork()) == 0) {
printf("[C] This is the child (pid %d).\n", getpid());
return 0;
}
printf("[P] Parent (pid %d) has spawned child (pid %d).\n", getpid(), pid);
// Call the 'continue_handler' when CONTINUE_SIGNAL is received.
act.sa_sigaction = continue_handler;
act.sa_flags = SA_SIGINFO;
sigaction(CONTINUE_SIGNAL, &act, NULL);
printf("[P] Waiting for CONTINUE_SIGNAL...\n");
// Block all signals except CONTINUE_SIGNAL and wait for it to occur.
sigfillset(&mask);
sigdelset(&mask, CONTINUE_SIGNAL);
sigsuspend(&mask);
printf("[P] Signal received. Exiting...\n");
return 0;
}
请注意,我已删除所有错误检查,以便能够以更紧凑的形式表示它。但是,我已经验证了以下两种情况下所有函数都成功返回。
在 Linux 系统上,代码完全符合我的预期:父进程生成一个子进程并等待SIGINT,然后继续执行continue_handler。
[P] Parent (pid 804) has spawned child (pid 805).
[P] Waiting for CONTINUE_SIGNAL...
[C] This is the child (pid 805).
<press Ctrl+C>
[P] Continuing process with pid = 804...
[P] Signal received. Exiting...
然而,在 macOS 上,我获得了以下输出(没有任何交互):
[P] Parent (pid 6024) has spawned child (pid 6025).
[P] Waiting for CONTINUE_SIGNAL...
[C] This is the child (pid 6025).
[P] Signal received. Exiting...
显然,父进程(pid 6024)的sigsuspend调用在子进程退出后立即返回。尽管SIGINT 似乎没有被触发,但sigsuspend 会返回EINTR 的errno,即报告一个非屏蔽信号成功终止。请注意,如果我阻止子进程退出,sigsuspend 会不自行返回,而是等待SIGINT 被传递。
我使用 API 的方式有问题吗?或者,POSIX 规范中是否存在一定程度的灵活性,可以使这两种行为都符合预期?
【问题讨论】:
-
您不能从信号处理程序中安全地调用
printf()。在严格符合 C 语言中,调用任何库函数都是不安全的。每footnote 188 of the C 11 standard:“因此,信号处理程序通常不能调用标准库函数。” POSIX allows for the calling of async-signal-safe functions 并且只有信号处理程序中的异步信号安全功能。printf()不是异步信号安全的。 -
@AndrewHenle 这不是这个程序关心的问题,因为只有当主要控制流在 sigsuspend 中被阻塞时才会传递信号。
-
@bosonic 恐怕我很难过。它几乎闻起来像 OSX 的 sigsuspend 实现中的一个错误。你能在一个完全开源的 BSD 上测试你的程序吗?如果您可以在那里重现相同的行为,那就更容易理解了。
-
这可以在没有 sigaction 并发症的情况下进行演示。设置一个五秒钟的闹钟。在终止之前分叉一个睡了一两秒钟的孩子。让父
sigsuspend只允许 SIGALRM。在 OS X 上,父级的sigsuspend将被 EINTRrupted 并继续执行;而在 Linux 上,父节点被闹钟终止。 -
@user3629249,信号处理程序和正常的收获做法是红鲱鱼。这里的问题是,为什么在 OS X 上,
sigsuspendwith SIGCHLD masked off被子进程终止中断了?