sigsuspend
更改信号掩码,暂停执行调用线程,直到收到一个"信号,其动作是执行信号捕获功能或终止进程",然后(如果进程未终止且信号处理程序返回)将信号掩码恢复到其原始状态。
POSIX.1-2008的链接页面并未说明多个信号是否可以在MaxBy<>
的单次调用中传递,也不能是否有关于信号掩模变化的原子性的说法;即在我看来,这是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;
}
与自身通信的程序(这是一个很长的故事),我需要一种方法来确保信号处理程序执行<每次内部调用SIGUSR1
时,只有一次,即使同一系统上的其他进程发送信号也是如此。
所以我的问题是:
由于这是相当抽象的,在我将喜欢的测试程序下总是打印1并成功退出,但我担心在某些情况下它可能会打印2或0,挂起直到报警熄火,或崩溃。 (C11原子用于过度谨慎;从技术上讲,你不能从信号处理程序中读取一个sigsuspend
,只有写到一个。)默认情况下使用SIGUSR1,如果在命令行上传递volatile sig_atomic_t
则使用SIGRTMIN。
-r
答案 0 :(得分:5)
这不是一个答案,而是一个探索性的程序。我希望有一种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 */
}
}
将上述内容保存为例如hack.c
,并使用
gcc -Wall -O2 hack.c -o hack
./hack SIGUSR1
提供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.
以上输出显示所有400,000次sigsuspend()
被调用,只传递了一个信号。 (但是,只有一个信号实例被传递,即使父母发送了8次:4个有不同的sigqueue()
有效载荷,4个有kill()`。)
正如我在评论中提到的,基于Linux内核源代码,我相信如果信号在{{1之前(之后)之前被阻塞,则Linux内核每sigsuspend()
次调用仅传送一个信号调用。上述输出支持这种信念。
运行相同的实时信号,比如sigsuspend()
,输出类似
./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.
次呼叫只发送一次。
该程序无法提供任何类型的证据证明系统的工作正如OP所希望的那样(或者Linux的工作原理我认为它的工作方式基于内核源代码)。它只能用于反驳它(在Linux上,我的信念)。如果有系统报告
sigsuspend()
如果#为2或更大,那么我们知道在该系统上,每次sigsuspend()调用都会传递多个(副本)信号。
(#0的情况只有在选择的信号是SIGINT,SIGHUP或SIGTERM时才会出现,这些信号也会被捕获,这样用户就可以停止该程序了。)
当然不能保证即使在这样的系统上运行,该程序也会设法偶然发现多个交付案例。
然而,让子进程停止,得到多个(a个副本)信号,然后继续,这将是系统每sigsuspend()调用传递多个(副本)信号的最佳点。如果在这种情况下不这样做,它会是什么情况呢?
答案 1 :(得分:3)
这是我在对原始问题的评论中提到的from turtle import Turtle, Screen
from math import pi
def polyline(turtle, n, length, angle):
for _ in range(n):
turtle.fd(length)
turtle.lt(angle)
def arc(turtle, radius, angle):
arc_length = 2 * pi * radius * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = float(angle) / n
polyline(turtle, n, step_length, step_angle)
def petal(turtle, radius, angle):
for _ in range(2):
arc(turtle, radius, angle)
turtle.lt(180 - angle)
def flower(turtle, n, radius, angle):
for _ in range(n):
petal(turtle, radius, angle)
turtle.lt(360 / n)
screen = Screen()
bob = Turtle()
flower(bob, 5, 77, 99)
screen.exitonclick()
/ sem_post()
解决方法的大纲。
这假设sem_wait()
通常被阻止;仅在SIGUSR1
来电期间取消阻止。
首先,信号处理程序被一个只发布专用信号量的简单信号处理程序替换:
sigsuspend()
每次调用sem_t sigusr1_semaphore;
void sigusr1_handler(int signum)
{
sem_post(&sigusr1_semaphore);
}
void sigusr1_work(void)
{
/* Whatever the original handler did */
}
int install_sigusr1_handler(void)
{
struct sigaction act;
sem_init(&sigusr1_semaphore, 0, 0);
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGUSR1);
act.sa_handler = sigusr1_handler;
act.sa_flags = 0;
if (sigaction(SIGUSR1, &act, NULL) == -1)
return errno; /* Failed */
return 0;
}
取消阻止上述信号处理程序的操作都会在它之后立即添加这段代码:
sigsuspend()
从技术上讲, if (!sem_trywait(&sigusr1_semaphore)) {
sigusr1_work();
while (!sem_trywait(&sigusr1_semaphore));
}
在实际工作完成之前返回。但是,由于工作是在同一个线程中完成的,如果工作在sigsuspend()
之后的语句之前完成,则应该没有真正的区别。毕竟,sigsuspend()
只影响线程信号掩码,因此排序(在工作和从sigsuspend()
返回之间)完全是线程内部的。
(编辑注意OP,zwol,指出有些情况确实很重要。在OP的情况下,信号处理程序使用备用堆栈,这意味着工作无法真正转移到正常的程序流程。 )
使用每线程原子变量显然可以实现相同的效果。
(目前,并非所有C编译器都以可移植的方式支持原子操作,但所有POSIXy系统都应支持信号量。我个人会使用原子操作,但包含在辅助函数中,以便在必要时轻松移植。)