了解信号,我想知道过程信号掩码、阻塞信号集、信号处理程序和阻塞信号之间的细微差别。
这些问题涉及(在 Debian 上):
- 西格普罗克面具(2)
- 西格塞托普斯(3) 相关函数
每个进程都有自己的信号掩码(一个包含被阻止的信号的长整型)。并且可以通过调用 sigprocmask(2) 来获得信号集,并使用 *set 变量的 NULL 参数,将导致旧的进程掩码被放入 *oldset 中,保持不变:
#include <string.h>
#include <signal.h>
void show_signals(const sigset_t exmask)
{
int exsignals[43];
exsignals[0] = SIGABRT;
exsignals[1] = SIGALRM;
exsignals[2] = SIGBUS;
exsignals[3] = SIGCHLD;
exsignals[4] = SIGCONT;
#ifdef SIGEMT
exsignals[5] = SIGEMT;
#else
exsignals[5] = -1;
#endif
exsignals[6] = SIGFPE;
#ifdef SIGFREEZE
exsignals[7] = SIGFREEZE;
#else
exsignals[7] = -1;
#endif
exsignals[8] = SIGHUP;
exsignals[9] = SIGILL;
#ifdef SIGINFO
exsignals[10] = SIGINFO;
#else
exsignals[10] = -1;
#endif
exsignals[11] = SIGINT;
exsignals[12] = SIGIO;
exsignals[13] = SIGIOT;
#ifdef SIGJVM1
exsignals[14] = SIGJVM1;
#else
exsignals[14] = -1;
#endif
#ifdef SIGJVM2
exsignals[15] = SIGJVM2;
#else
exsignals[15] = -1;
#endif
exsignals[16] = SIGKILL;
#ifdef SIGLOST
exsignals[17] = SIGLOST;
#else
exsignals[17] = -1;
#endif
#ifdef SIGLWP
exsignals[18] = SIGLWP;
#else
exsignals[18] = -1;
#endif
exsignals[19] = SIGPIPE;
exsignals[20] = SIGPOLL;
exsignals[21] = SIGPROF;
exsignals[22] = SIGPWR;
exsignals[23] = SIGQUIT;
exsignals[24] = SIGSEGV;
exsignals[25] = SIGSTKFLT;
exsignals[26] = SIGSTOP;
exsignals[27] = SIGSYS;
exsignals[28] = SIGTERM;
#ifdef SIGTHAW
exsignals[29] = SIGTHAW;
#else
exsignals[29] = -1;
#endif
#ifdef SIGTHR
exsignals[30] = SIGTHR;
#else
exsignals[30] = -1;
#endif
exsignals[31] = SIGTRAP;
exsignals[32] = SIGTSTP;
exsignals[33] = SIGTTIN;
exsignals[34] = SIGTTOU;
exsignals[35] = SIGURG;
exsignals[36] = SIGUSR1;
exsignals[37] = SIGUSR2;
exsignals[38] = SIGVTALRM;
#ifdef SIGWAITING
exsignals[39] = SIGWAITING;
#else
exsignals[39] = -1;
#endif
exsignals[40] = SIGWINCH;
exsignals[41] = SIGXCPU;
exsignals[42] = SIGXFSZ;
#ifdef SIGXRES
exsignals[43] = SIGXRES;
#else
exsignals[43] = -1;
#endif
int exsignals_n = 0;
for (;exsignals_n < 43; exsignals_n++) {
if (exsignals[exsignals_n] == -1) continue;
static char *exsignal_name;
exsignal_name = strsignal(exsignals[exsignals_n]);
switch(sigismember(&exmask, exsignals[exsignals_n]))
{
case 0: break;
case 1: printf("YES %sn", exsignal_name); break;
case -1: printf("could not obtain signaln"); break;
default: printf("UNEXPECTED for %s returnn", exsignal_name); break;
}
}
}
const sigset_t getmask(void)
{
static sigset_t retmask;
if ((sigprocmask(SIG_SETMASK, NULL, &retmask)) == -1)
printf("could not obtain process signal maskn");
return retmask;
}
在我的程序开始时,我意识到过程信号掩码,没有阻挡任何信号。然后,我将信号处理程序放入程序中。
static void sig_abrt(int signo)
{
printf("Caught SIGABRTn");
}
int main(void)
{
show_signals(getmask());
signal(SIGABRT, sig_abrt);
show_signals(getmask());
return 0;
}
所以现在有一个用于 SIGABRT 的信号处理程序,但是如果我再次调用 sigprocmask(2),如上所述,SIGABRT 将不会在进程中出现信号掩码。我尝试使用 sigismember(3) 进行检查,但是只有在我调用 sigaddset(3) 或其他修改信号掩码的函数后,才会修改进程信号掩码。
如果我使用 sigaddset(3) 阻塞 SIGABRT,信号处理程序在 SIGABRT 交付时sig_abrt收不到调用吗?这是否意味着信号模板会影响传递哪些信号?有什么区别?
另外,有没有办法在不使用 sigsetops(3) 和 sigprocmask(2) 函数的情况下阻止进程中的信号?
每个进程都有自己的信号掩码(包含被阻止的信号的长
)
嗯,没有。信号掩码实际上是特定于线程的。(在多线程程序中,必须使用pthread_sigmask()
来操作当前线程的信号掩码;在单线程程序中,可以使用sigprocmask()
。
此外,它不是"很长"。它属于sigset_t
类型,可能是数组、结构或联合类型。在任何情况下,都应该简单地将其视为一个无序位集,每个信号一个位。
所以现在有一个用于SIGABRT的信号处理程序,但SIGABRT不会在过程中信号模板中。
正确。无论您是否分配了信号处理程序,都不会影响信号掩码。
如果我使用 sigaddset(3) 阻塞 SIGABRT,信号处理程序在 SIGABRT 交付时sig_abrt收不到调用吗?这是否意味着信号模板会影响传递哪些信号?有什么区别?
如果所有线程都阻塞了 SIGABRT,则在信号被解锁(从信号掩码中删除)之前,它不会传递。如果使用sigwait()
、sigwaitinfo()
或sigtimedwait()
消耗信号,则根本不会调用信号处理程序。
简短总结:
-
信号可以定向到进程组(
kill()
pid == 0
或pid == -pgid
)、特定进程(pid
)或特定进程中的特定线程(pthread_kill()
在同一进程中,tgkill
Linux中的系统调用)。 -
如果信号被定向到进程组,则该组中的每个进程都会接收信号的"副本"。
信号 掩码定义信号是被阻止还是立即传递。
在每个过程中,每个信号
可以有一个信号处理程序,或者
被忽略(
SIG_IGN
"处理程序"),或具有默认处置(忽略 (Ign),使用(核心)或不使用(术语)核心转储终止进程;或者它可以停止(停止)或继续(续)目标线程或进程的执行)。有关详细信息,请参阅
man 7 signal
。
如果某些(但不是全部)线程阻塞了信号,并且该信号未针对特定线程,则内核会将信号定向到未阻塞信号的线程之一(随机)。
有两种方法可以捕获信号:
使用信号处理程序。仅当信号未被阻止时,信号才会传递到信号处理程序。如果信号被阻塞,则信号的传递将挂起,直到未被阻塞(或被下面的其他选项捕获)。
sigwait()
、sigwaitinfo()
或sigtimedwait()
。这些函数检查是否有任何信号挂起,如果是,则"捕获"它。他们检查的信号集由sigset_t
类型的函数参数定义。
当内核向进程发送/转发信号时,它首先检查进程是否有未阻止该信号的线程。如果有这样的线程,它会通过该线程传递它。(如果信号具有信号处理程序,则会在该线程中调用该信号处理程序;否则,效果由信号处置决定。
如果信号被阻塞,内核会将其保留为等待进程。
如果进程使用指定信号集中的挂起信号调用sigwait()
、sigwaitinfo()
或sigtimedwait()
,则接收有关该信号的信息,并捕获该信号。(它将不再处于挂起状态,并且不会导致调用信号处理程序;它被"消耗"。
如果进程更改其信号掩码,以便挂起的信号被解锁,则它由内核传递(就像在该时间点发送一样)。
另外,有没有办法在不使用 sigsetops(3) 和 sigprocmask(2) 函数的情况下阻止进程中的信号?
不。(您可以实现自己的sigsetops()
和用于sigprocmask()
的系统调用包装器,但仅此而已。
下面是一个示例程序example.c,可用于在单线程进程中探索信号处理程序、捕获信号和信号掩码:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
/* Async-signal safe write-to-standard error function.
Keeps errno unchanged. Do not use stderr otherwise!
*/
static int wrerrpp(const char *ptr, const char *end)
{
const int saved_errno = errno;
ssize_t chars;
while (ptr < end) {
chars = write(STDERR_FILENO, ptr, (size_t)(end - ptr));
if (chars > 0)
ptr += chars;
else
if (chars != -1) {
errno = saved_errno;
return EIO;
} else
if (errno != EINTR) {
const int retval = errno;
errno = saved_errno;
return retval;
}
}
errno = saved_errno;
return 0;
}
/* Write the supplied string to standard error.
Async-signal safe. Keeps errno unchanged.
Do not mix with stderr!
*/
static int wrerr(const char *ptr)
{
if (!ptr)
return 0;
else {
const char *end = ptr;
/* strlen() is not async-signal safe, so
find the end of the string the hard way. */
while (*end)
end++;
return wrerrpp(ptr, end);
}
}
/* Write the supplied long to standard error.
Async-signal safe. Keeps errno unchanged.
Do not mix with stderr!
*/
static int wrerrnum(const long value)
{
unsigned long u = (value < 0) ? (unsigned long)-value : (unsigned long)value;
char buf[40];
char *ptr = buf + sizeof buf;
char *const end = buf + sizeof buf;
do {
*(--ptr) = '0' + (u % 10uL);
u /= 10uL;
} while (u > 0uL);
if (value < 0)
*(--ptr) = '-';
return wrerrpp(ptr, end);
}
/* Async-signal safe variant of strsignal().
Only covers a small subset of all signals.
Returns NULL if the signal name is not known. */
static const char *signal_name(const int signum)
{
switch (signum) {
case SIGHUP: return "HUP";
case SIGINT: return "INT";
case SIGQUIT: return "QUIT";
case SIGKILL: return "KILL";
case SIGSEGV: return "SEGV";
case SIGTERM: return "TERM";
case SIGUSR1: return "USR1";
case SIGUSR2: return "USR2";
case SIGCHLD: return "CHLD";
case SIGCONT: return "CONT";
case SIGSTOP: return "STOP";
default: return NULL;
}
}
/* Signal handler that reports its delivery immediately,
but does nothing else.
*/
static void report_signal(int signum, siginfo_t *info, void *ctx)
{
const char *sname = signal_name(signum);
wrerr("report_signal(): Received signal ");
if (sname)
wrerr(sname);
else
wrerrnum(signum);
if (info->si_pid) {
wrerr(" from process ");
wrerrnum(info->si_pid);
wrerr(".n");
} else
wrerr(" from kernel or terminal.n");
}
/* Install report_signal() handler.
*/
static int install_report_signal(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_sigaction = report_signal;
act.sa_flags = SA_SIGINFO;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int main(void)
{
sigset_t mask;
siginfo_t info;
const char *name;
int signum;
if (install_report_signal(SIGINT) ||
install_report_signal(SIGCONT)) {
const char *errmsg = strerror(errno);
wrerr("Cannot install signal handlers: ");
wrerr(errmsg);
wrerr(".n");
return EXIT_FAILURE;
}
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_SETMASK, &mask, NULL);
printf("Process %ld is ready to receive signals! Runn", (long)getpid());
printf("tkill -USR1 %ldn", (long)getpid());
printf("tkill -USR2 %ldn", (long)getpid());
printf("tkill -HUP %ldn", (long)getpid());
printf("tkill -TERM %ldn", (long)getpid());
printf("in another terminal; press Ctrl+C in this terminal; or press Ctrl+Z and runn");
printf("tfgn");
printf("in this terminal.n");
fflush(stdout);
/* Almost same as blocked mask, just without SIGUSR1 and SIGUSR2. */
sigemptyset(&mask);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
do {
do {
signum = sigwaitinfo(&mask, &info);
} while (signum == -1 && errno == EINTR);
if (signum == -1) {
const char *errmsg = strerror(errno);
wrerr("sigwaitinfo(): ");
wrerr(errmsg);
wrerr(".n");
return EXIT_FAILURE;
}
name = signal_name(signum);
if (name)
printf("main(): Received signal %s from ", name);
else
printf("main(): Received signal %d from ", signum);
if (info.si_pid == 0)
printf("kernel or terminal.n");
else
printf("process %ld.n", (long)info.si_pid);
fflush(stdout);
} while (signum != SIGTERM);
return EXIT_SUCCESS;
}
例如编译它
gcc -Wall -O2 example.c -o example
我建议你准备两个终端。在一个终端中,使用
./example
并观察其输出。它将是这样的
进程 843 已准备好接收信号!跑
杀死 -USR1 843
杀死 -USR2 843
杀 -HUP 843
杀 - 第843条
在另一个终端中;在此终端中按 Ctrl+C;或按 Ctrl+Z 并运行
盖瑞
在这个终端中。
无法捕获 KILL 和 STOP 信号。KILL 将始终终止进程,而 STOP 将始终停止("暂停")进程。
如果在该终端中按Ctrl+C,内核将向进程发送 INT 信号。(这将通过report_signal()
信号处理程序提供。
如果在该终端中按Ctrl+Z,内核将向进程发送 STOP 信号。shell 检测到这一点,将./example
推送到作业控制下,并允许您输入新的 shell 命令。fg
命令将./example
带回前台,shell 向其发送 CONT 信号,以便./example
将继续执行。
USR1 和 USR2 信号被阻止,因此它们永远不会传递到report_signal()
信号处理程序。
HUP和TERM信号也被阻塞,但它们被主线程通过sigwaitinfo()
接收。
当程序收到 TERM 信号时,程序退出。