zwo*_*wol 9 c posix signals language-lawyer
sigsuspend 更改信号掩码,暂停执行调用线程,直到它收到"其动作是执行信号捕获函数或终止进程的信号",然后(如果进程未终止且信号处理程序返回)将信号掩码恢复到其原始状态.
POSIX.1-2008的链接页面没有说明是否可以在一次调用中传递多个信号sigsuspend,也没有说明信号掩码变化的原子性; 即使在我看来,这是一个符合要求的实现sigsuspend,即使整个观点sigsuspend是它没有这个代码所具有的竞争条件:
int sigsuspend(const sigset_t *mask)
{
sigset_t oldmask;
if (sigprocmask(SIG_SETMASK, mask, &oldmask)) return -1;
pause();
if (sigprocmask(SIG_SETMASK, &oldmask, 0)) return -1;
return -1;
}
Run Code Online (Sandbox Code Playgroud)
我实际上担心的场景是一个SIGUSR1用于与自身通信的程序(这是一个很长的故事),我需要一种方法来确保信号处理程序每次内部调用只执行一次sigsuspend,即使其他进程也是如此系统发送信号.
所以我的问题是:
由于这是相当抽象,下面的测试程序,我会喜欢总是打印1和顺利退出,但我担心,在某些情况下,可能打印2或0,挂起,直到警报响起,或崩溃.(C11原子用于过度谨慎;从技术上讲,你不允许从信号处理程序读取一个volatile sig_atomic_t,只写一个.)它默认使用SIGUSR1,如果传递-r命令行则使用SIGRTMIN .
#define _XOPEN_SOURCE 700
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#ifndef ATOMIC_INT_LOCK_FREE
#error "This program uses atomic_uint from a signal handler."
#endif
static atomic_uint handler_call_count;
static pid_t self_pid;
static void perror_exit(const char *msg)
{
perror(msg);
exit(1);
}
static void handler(int signo)
{
union sigval dummy;
dummy.sival_int = 0;
if (handler_call_count++ == 0)
if (sigqueue(self_pid, signo, dummy))
perror_exit("sigqueue");
}
int main(int argc, char **argv)
{
sigset_t mask1, mask2;
struct sigaction sa;
int signo;
union sigval dummy;
if (argc > 1 && !strcmp(argv[1], "-r"))
signo = SIGRTMIN;
else
signo = SIGUSR1;
sigemptyset(&mask1);
sigemptyset(&sa.sa_mask);
sigaddset(&mask1, signo);
sigaddset(&sa.sa_mask, signo);
if (sigprocmask(SIG_BLOCK, &mask1, &mask2))
perror_exit("sigprocmask");
sigdelset(&mask2, SIGALRM);
sigdelset(&mask2, signo);
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;
if (sigaction(signo, &sa, 0))
perror_exit("sigaction");
self_pid = getpid();
dummy.sival_int = 0;
if (sigqueue(self_pid, signo, dummy))
perror_exit("sigqueue");
alarm(5);
sigsuspend(&mask2);
alarm(0);
printf("%u\n", atomic_load(&handler_call_count));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
不.事实上,建议采用相反的方法.POSIX 2008 第4卷:基本原理,§B.2.4.1信号生成和交付状态:
当有多个未被阻止的未决信号时,如果可能的话,实现应该安排一次传递所有信号.一些实现将所有未决信号捕获例程的调用堆叠起来,使得每个信号捕获器看起来都被下一个信号中断.在这种情况下,实现应该确保这种信号堆叠不违反由其建立的信号掩码的语义
sigaction().其他实现在进入操作系统时最多处理一个信号,保留剩余信号以便以后传送.虽然这种做法很普遍,但这种行为既没有标准化也没有得到认可.在任何一种情况下,如果可能的话,实现应该尝试SIGFPE在其他信号之前传递与过程的当前状态相关联的信号(例如,).
此外,它是可以具有相同数目的信号的多个信号被同时排队.POSIX 2008 第2卷:系统接口,§2.4.1信号生成和传递状态
如果生成后续出现的未决信号,则在实现中定义信号是否在需要排队的情况以外的情况下被多次传送或接受.未指定SIGRTMIN到SIGRTMAX范围之外的多个同时未决信号被传送到过程或由过程接受的顺序.
这也适用于实时信号.POSIX 2008 第2卷:系统接口,§2.4.2实时信号生成和传送状态
[...]如此生成的多次出现的信号按FIFO顺序排队.[...]
如果在传递待处理信号时,还有其他信号排队到该信号编号,则该信号应保持未决状态.否则,应重置待处理的指示.
合法辩论.这可能是有意的,但实际上并没有规范地说明.有好的论据支持和反对:
POSIX 2008在第2卷:系统接口,§3pause()中强烈暗示原子性:
申请使用
许多常见的用途
pause()有定时窗口.该方案涉及检查与信号相关的条件,如果没有发生信号,则调用pause().当检查和调用之间发生信号时pause(),该过程通常会无限期地阻塞.的sigprocmask()和sigsuspend()功能可用于避免这种类型的问题.
RATIONALE标题下出现了类似的强烈暗示sleep().此外,单词can在POSIX 2008 第1卷:基本定义,§1.5术语中定义为:
出于POSIX.1-2008的目的,以下术语定义适用:
能够
描述用户或应用程序可用的允许的可选功能或行为.对于符合POSIX.1-2008的实现,该功能或行为是必需的.应用程序可以依赖于特征或行为的存在.
和POSIX 2008的第4卷:基本原理,§A.1.5术语陈述:
可以
may的使用受到了尽可能的限制,原因是由于其普通的英语含义造成的混淆,以及对尽可能少的选择和尽可能明确规定的选择的可取性的反对意见.
选择can和may的用法以将可选应用程序行为(可以)与可选实现行为(可能)进行对比.
也就是说,pause()的实际应用信息标题指出应用程序没有义务调用sigsuspend()以避免时间上的问题,但它应该选择,.然后POSIX.1-2008符合的实现sigsuspend()必须避免说时间窗口的问题(例如,在内核的帮助下是原子的).
如果sigsuspend()不是原子的,那么
sigsuspend()是错误的,因此自1988年第一个版本以来所有版本的POSIX.1标准都是内部不一致的.sigsuspend()会没用,因为它没有任何目的pause().然后,人们必须要问为什么POSIX.1将包括自第一个版本以来的两个,以及sigsuspend()优先使用的错误建议pause().POSIX 2008 第2卷:系统接口,§1.2条目格式状态:
[...]
申请使用
本节内容丰富.
本节向应用程序开发人员提供有关该条目的警告和建议.如果警告和建议与本卷POSIX.1-2008的规范部分发生冲突,则应将规范性材料视为正确.
基本原理
本节内容丰富.
本节包含有关本卷POSIX.1-2008内容的历史信息,以及标准开发人员包含或丢弃功能的原因.
[...]
这意味着这些部分是信息性的,而不是规范性的,只有规范性部分才能直接对实施者施加要求.然而,他们可以帮助解释标准,并声明说:" 在sigprocmask()和sigsuspend()功能可以用来避免这种类型的问题, "不符合标准的规范性部分冲突.
POSIX 2008的第4卷:基本原理,§A.1.5术语陈述:
实现定义
该定义类似于ISO C标准的定义,并且与"未定义"和"未指定"一起提供了一系列允许接口实现者自由的规范.
[...]
不明
请参阅实现定义.
[...]
在许多地方,POSIX.1-2008对某些可能的构造的行为保持沉默.例如,可以为指定的值范围定义变量,并为这些值描述行为; 如果变量具有任何其他值,会发生什么事情.这种沉默可能意味着标准中的错误,但它也可能暗示标准是故意保持沉默并且允许任何行为.如果标准是沉默的,则自然倾向于推断出一种行为是被禁止的.这不是意图.沉默旨在等同于"未指定"一词.
ISO C标准(目前,C11)将"实现定义的行为"定义为实现可以选择的行为,但必须记录其选择.
没有关于原子性的任何规范性陈述sigsuspend()可能是错误或等同于明确声明它是实现定义的行为.
pause()并sleep()表明它是标准中的错误.sigsuspend()原子性是实现定义的,优先于相反的信息性陈述.那么我们可以从野外实施中激励自己吗?也许.我知道没有没有原子实现它的实现:
sigsuspend()以原子方式实现.sigsuspend()以原子方式实现.这不是一个答案,而是一个探索性的计划.我希望有一种macOS和/或*BSD用户可以在他们的机器上测试它,并报告他们的结果.
出于兴趣,我编写了一个粗略的程序来评估每次调用多于一个信号的传递频率sigsuspend().
以下程序中的想法是让用户在命令行上指定信号.该过程将分叉子进程.
子进程阻塞该信号,然后安装一个原子(使用GCC内置函数)递增计数器的信号处理程序.然后它将进入一个循环,在那里它自动停止(通过raise(SIGSTOP)).父进程将检测到这一点,然后发送一组信号SIGCONT.当子进程唤醒时,它会调用,sigsuspend()直到没有更多信号待处理.对于每个sigsuspend()呼叫,计算传送的信号的数量.在没有待处理的信号之后,孩子再次停止.
父母发送一半(副本)信号使用kill(),其余使用sigqueue()不同的有效载荷.
这是一个彻底的黑客,可能根本不遵循POSIX.1标准,可能包含很多错误.(如果您发现任何问题,请在评论中告诉我,以便我可以解决.)
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#ifndef CLUSTER
#define CLUSTER 8
#endif
#ifndef INTERVAL
#define INTERVAL 100000
#endif
static const struct {
const int number;
const char name[8];
} signal_list[] = {
{ SIGHUP, "HUP" },
{ SIGINT, "INT" },
{ SIGQUIT, "QUIT" },
{ SIGILL, "ILL" },
{ SIGABRT, "ABRT" },
{ SIGFPE, "FPE" },
{ SIGSEGV, "SEGV" },
{ SIGPIPE, "PIPE" },
{ SIGALRM, "ALRM" },
{ SIGTERM, "TERM" },
{ SIGUSR1, "USR1" },
{ SIGUSR2, "USR2" },
{ SIGTSTP, "TSTP" },
{ SIGTTIN, "TTIN" },
{ SIGTTOU, "TTOU" },
{ SIGBUS, "BUS" },
{ SIGPOLL, "POLL" },
{ SIGPROF, "PROF" },
{ SIGSYS, "SYS" },
{ SIGTRAP, "TRAP" },
{ SIGURG, "URG" },
{ SIGVTALRM, "VTALRM" },
{ SIGXCPU, "XCPU" },
{ SIGXFSZ, "XFSZ" },
{ SIGIO, "IO" },
{ SIGPWR, "PWR" },
{ SIGWINCH, "WINCH" },
{ -1, "" }
};
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
done = 1;
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
static unsigned long counter = 0UL;
static void increment_counter(void) { __atomic_add_fetch(&counter, 1UL, __ATOMIC_SEQ_CST); }
static unsigned long get_and_clear_counter(void) { return __atomic_fetch_and(&counter, 0UL, __ATOMIC_SEQ_CST); }
static void handle_counter(int signum)
{
increment_counter();
}
static int install_counter(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, signum);
act.sa_handler = handle_counter;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int child_process(int signum)
{
sigset_t signals, no_signals, pending;
unsigned long count, corrects, incorrects, cluster, noncluster, clustercount, nonclustercount;
int result, exitcode;
sigemptyset(&no_signals);
sigemptyset(&signals);
sigaddset(&signals, signum);
if (sigprocmask(SIG_BLOCK, &signals, NULL) == -1) {
fprintf(stderr, "Child: Cannot block the signal: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM) ||
install_counter(signum)) {
fprintf(stderr, "Child: Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Ready to wait for signals to become pending. */
exitcode = EXIT_SUCCESS;
corrects = 0UL; incorrects = 0UL;
cluster = 0UL; clustercount = 0UL;
noncluster = CLUSTER; nonclustercount = 0UL;
raise(SIGSTOP);
while (1) {
if (done)
return exitcode;
sigemptyset(&pending);
if (sigpending(&pending) == -1) {
fprintf(stderr, "Child: Sigpending failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (!sigismember(&pending, signum)) {
if (cluster != CLUSTER) {
if (cluster != noncluster) {
fprintf(stderr, "Child: Signals are delivered in clusters of %lu signals; expected %d.\n", cluster, CLUSTER);
noncluster = cluster;
}
nonclustercount++;
} else
clustercount++;
if ((clustercount + nonclustercount) % INTERVAL == 0UL) {
if (incorrects > 0UL)
printf("Child: %lu times out of %lu times only one signal is delivered per sigsuspend() call.\n", corrects, corrects + incorrects);
else
printf("Child: All %lu times sigsuspend() was called, only one signal was delivered.\n", corrects);
if (clustercount > 0UL && nonclustercount > 0UL)
printf("Child: In %lu of %lu sets of signals, all %d copies of the signal were delivered.\n", clustercount, clustercount + nonclustercount, CLUSTER);
else
if (clustercount > 0UL)
printf("Child: In all %lu sets of signals, all %d copies of the signal were delivered.\n", clustercount, CLUSTER);
fflush(stdout);
}
cluster = 0UL;
raise(SIGSTOP);
}
if (done)
return exitcode;
result = sigsuspend(&no_signals);
if (result != -1 || errno != EINTR) {
printf("Child: sigsuspend() returned %d, expected -1 with errno == EINTR!\n", result);
return EXIT_FAILURE;
}
if (done)
return exitcode;
count = get_and_clear_counter();
cluster += count;
if (count != 1UL) {
printf("Child: Received %lu signals on one sigsuspend() call!\n", count);
fflush(stdout);
exitcode = EXIT_FAILURE;
++incorrects;
} else
++corrects;
}
}
int parse_signum(const char *name)
{
unsigned int u;
int i;
char c;
if (!name || !*name) {
errno = EINVAL;
return -1;
}
if (name[0] == 'S' &&
name[1] == 'I' &&
name[2] == 'G' &&
name[3] != '\0')
for (i = 0; signal_list[i].number >= 0; i++)
if (!strcmp(name + 3, signal_list[i].name))
return signal_list[i].number;
for (i = 0; signal_list[i].number >= 0; i++)
if (!strcmp(name, signal_list[i].name))
return signal_list[i].number;
if ((sscanf(name, " SIGRT%u %c", &u, &c) == 1 ||
sscanf(name, " RT%u %c", &u, &c) == 1 ||
sscanf(name, " SIGRTMIN+%u %c", &u, &c) == 1 ||
sscanf(name, " RTMIN+%u %c", &u, &c) == 1) &&
u <= (unsigned int)(SIGRTMAX - SIGRTMIN))
return SIGRTMIN + u;
if ((sscanf(name, " SIGRTMAX-%u %c", &u, &c) == 1 ||
sscanf(name, " RTMAX-%u %c", &u, &c) == 1) &&
u <= (unsigned int)(SIGRTMAX - SIGRTMIN))
return SIGRTMAX - u;
errno = EINVAL;
return -1;
}
int main(int argc, char *argv[])
{
pid_t child, p;
int signum, i, status;
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s [ -l | --list ]\n", argv[0]);
fprintf(stderr, " %s SIGNAL\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program uses a stopped child process to see if\n");
fprintf(stderr, "a single call to sigsuspend() can cause more than\n");
fprintf(stderr, "one SIGNAL to be delivered.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
if (!strcmp(argv[1], "-l") || !strcmp(argv[1], "--list")) {
fprintf(stderr, "List of known standard POSIX signals:\n");
for (i = 0; signal_list[i].number >= 0; i++)
fprintf(stderr, "\tSIG%-7s (%d)\n", signal_list[i].name, signal_list[i].number);
fprintf(stderr, "POSIX realtime signals can be referred to as\n");
fprintf(stderr, "\tSIGRTMIN+0 or SIGRTMAX-%d\n", SIGRTMAX-SIGRTMIN);
fprintf(stderr, "\t to\n");
fprintf(stderr, "\tSIGRTMIN+%d or SIGRTMAX-0\n", SIGRTMAX-SIGRTMIN);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Parent: Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
signum = parse_signum(argv[1]);
if (signum < 0) {
fprintf(stderr, "%s: Unknown signal.\n", argv[1]);
return EXIT_FAILURE;
}
if (signum >= SIGRTMIN && signum <= SIGRTMAX)
fprintf(stderr, "Using POSIX realtime signal number %d (SIGRTMIN%+d)\n", signum, signum-SIGRTMIN);
else
fprintf(stderr, "Using standard POSIX signal number %d.\n", signum);
child = fork();
if (child == (pid_t)-1) {
fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else
if (!child)
return child_process(signum);
/* Parent process. */
while (!done) {
/* Wait for child to become stopped or continued. */
while (!done) {
do {
p = waitpid(child, &status, WUNTRACED | WCONTINUED);
} while (!done && p == (pid_t)-1 && errno == EINTR);
if (done)
break;
if (p == (pid_t)-1) {
if (errno == EINTR)
continue;
fprintf(stderr, "Parent: Child process vanished: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else
if (p != child)
continue;
if (WIFSTOPPED(status) || WIFCONTINUED(status))
break;
if (WIFEXITED(status)) {
if (WEXITSTATUS(status))
fprintf(stderr, "Parent: Child exited with exit status %d.\n", WEXITSTATUS(status));
else
fprintf(stderr, "Parent: Child exited successfully.\n");
} else
if (WIFSIGNALED(status))
fprintf(stderr, "Parent: Child died from signal %d.\n", WTERMSIG(status));
else
fprintf(stderr, "Parent: Lost child process.\n");
return EXIT_FAILURE;
}
if (done)
break;
if (WIFSTOPPED(status)) {
/* Send child a total of CLUSTER signals.
Half of them using sigqueue(), half via kill().
*/
i = 0;
while (i < CLUSTER) {
union sigval sv;
sv.sival_int = ++i;
sigqueue(child, signum, sv);
if (i++ < CLUSTER)
kill(child, signum);
}
/* Wake child up. */
kill(child, SIGCONT);
}
}
/* Tell the child process to terminate. */
kill(child, SIGCONT);
kill(child, signum);
kill(child, SIGTERM);
while (1) {
p = waitpid(child, &status, 0);
if (p == (pid_t)-1) {
if (errno == EINTR)
continue;
return EXIT_FAILURE;
}
if (p == child)
return status; /* HACK to return the child process status as-is */
}
}
Run Code Online (Sandbox Code Playgroud)
保存以上内容,例如hack.c,使用编译和运行它
gcc -Wall -O2 hack.c -o hack
./hack SIGUSR1
Run Code Online (Sandbox Code Playgroud)
提供OP担心的案例的输出.在Linux on x86-64架构(内核4.4.0,GCC 5.4.0)中,它输出类似的东西
Using standard POSIX signal number 10.
Child: Signals are delivered in clusters of 1 signals; expected 8.
Child: All 100000 times sigsuspend() was called, only one signal was delivered.
Child: All 200000 times sigsuspend() was called, only one signal was delivered.
Child: All 300000 times sigsuspend() was called, only one signal was delivered.
Child: All 400000 times sigsuspend() was called, only one signal was delivered.
Run Code Online (Sandbox Code Playgroud)
上面的输出显示所有400,000次sigsuspend()被调用,只传递了一个信号.(但是,只有一个信号实例被传送,即使父母发送了8次:四个有不同的sigqueue()有效负载,四个有kill()`.)
正如我在评论中提到的,基于Linux内核源代码sigsuspend(),如果信号在sigsuspend()调用之前(以及之后)被阻塞,我相信Linux内核每次调用只传递一个信号.以上输出支持这种信念.
运行相同的实时信号,比如./hack SIGRTMIN+0输出类似的东西
Using POSIX realtime signal number 34 (SIGRTMIN+0)
Child: All 800000 times sigsuspend() was called, only one signal was delivered.
Child: In all 100000 sets of signals, all 8 copies of the signal were delivered.
Child: All 1600000 times sigsuspend() was called, only one signal was delivered.
Child: In all 200000 sets of signals, all 8 copies of the signal were delivered.
Child: All 2400000 times sigsuspend() was called, only one signal was delivered.
Child: In all 300000 sets of signals, all 8 copies of the signal were delivered.
Child: All 3200000 times sigsuspend() was called, only one signal was delivered.
Child: In all 400000 sets of signals, all 8 copies of the signal were delivered.
Run Code Online (Sandbox Code Playgroud)
这表明在这次运行中,信号的每个实例都被传送,每次sigsuspend()呼叫一次.
该程序无法提供任何类型的证据证明系统正如OP所希望的那样(或者Linux的工作原理我认为它的工作方式基于内核源代码).它只能用于反驳它(在Linux上,我的信念).如果有系统报告
Child: Received # signals on one sigsuspend() call!
Run Code Online (Sandbox Code Playgroud)
如果#为2或更大,那么我们知道在该系统上,每次sigsuspend()调用都会传递多个(副本)信号.
(只有当选择的信号是SIGINT,SIGHUP或SIGTERM时,#0的情况才会出现,这些信号也被捕获,这样用户就可以停止该程序了.)
当然不能保证即使在这样的系统上运行,该程序也会设法偶然发现多个交付情况.
但是,让子进程停止,获取多个(a个副本)信号,然后继续,这将是系统每sigsuspend()调用传递多个(副本)信号的最佳点.如果在这种情况下不这样做,那会是什么情况呢?