信号集 / 信号掩码(阻塞信号传递)
【摘自《Linux/Unix系统编程手册》】
信号集
sigemptyset() 函数初始化一个未包含任何成员的信号集。sigfillset() 函数则初始化一个信号集,使其包含所有信号(包括所有实时信号)。
#include <signal.h> int sigemptyset(sigset_t* set); int sigfillset(sigset_t* set); Both return 0 on success, or -1 on error
必须使用 sigemptyset() 或者 sigfillset() 来初始化信号集。这是因为 C 语言不会对自动变量进行初始化,并且,借助于将静态变量初始化为 0 的机制来表示空信号集的作法在可移植性上存在问题,因为有可能使用位掩码之外的结构来实现信号集。(出于同一原因,为将信号集标记为空而使用 memset 函数将其内容清零的做法也不正确)
信号集初始化后,可以分别使用 sigaddset() 和 sigdelset() 函数向一个集合中添加或者移除单个信号。
#include <signal.h> int sigaddset(sigset_t* set, int sig); int sigdelset(sigset_t* set, int sig); Both return 0 on success, or -1 on error
sigismember() 函数用来测试信号 sig 是否是信号集 set 的成员
sigismember(const sigset_t* set, int sig); Return 1 if sig is a member of set, otherwise 0
GNU C 库还实现了 3 个非标准函数,是对上述信号集标准函数的补充。
#define _GNU_SOURCE #include <signal.h> int sigandset(sigset_t* dest, sigset_t* left, sigset_t* right); int sigorset(sigset_t* dest, sigset_t* left, sigset_t* right); Both return 0 on success, or -1 on error int sigisemptyset(const sigset_t* set); Return 1 if sig is empty, otherwise 0
sigandset() 将 left 集和 right 集的交集置于 dest 集。
sigorset() 将 left 集和 right 集的并集置于 dest 集。
若 set 集内未包含信号,则 sigisemptyset() 返回 true。
信号掩码(阻塞信号传递)
内核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程的传递。如果将遭阻塞的信号发给某进程,那么对该信号的传递将延后,直至从进程信号掩码中移除该信号,从而解除阻塞为止。(信号掩码实际属于线程属性,在多线程进程中,每个线程都可使用 pthread_sigmask() 函数来独立检查和修改其信号掩码。)
向信号掩码中添加一个信号,有如下几种方式:
- 当调用信号处理器程序时,可将引发调用的信号自动添加到信号掩码中。是否发生这一情况,要视 sigaction() 函数在安装信号处理器程序时所使用的标志而定
- 使用 sigaction() 函数建立信号处理器程序时,可以指定一组额外信号,当调用该处理器程序时会将其阻塞
- 使用 sigprocmask() 系统调用,随时可以显式地向信号掩码中添加或移除信号
#include <signal.h> int sigprocmask(int how, const sigset_t* set, sigset_t* oldset); Returns 0 on success, or -1 on error
使用 sigprocmask() 函数即可修改进程的信号掩码,又可获取现有掩码,或者两重功效兼具。how 参数指定了 sigprocmask() 函数想给信号掩码带来的变化:
- SIG_BLOCK:将 set 指向信号集内的指定信号添加到信号掩码中。换言之,将信号掩码设置为其当前值和 set 的并集
- SIG_UNBLOCK:将 set 指向信号集中的信号从信号掩码中移除。即使要解除阻塞的信号当前并未处于阻塞状态,也不会返回错误。
- SIG_SETMASK:将 set 指向的信号集赋给信号掩码
如果想获取信号掩码而又对其不做改动,那么可将 set 参数指定为空,这时将忽略 how 参数。
要想暂时阻止信号的传递,可以使用下面的调用来阻塞信号,然后再将信号掩码重置为先前的状态以解除对信号的锁定:
1 sigset_t blockSet, prevMask; 2 3 /* Initialize a signal set to contain SIGINT */ 4 sigemptyset(&blockSet); 5 sigaddset(&blockSet, SIGINT); 6 7 /* Block SIGINT, save previous signal mask */ 8 if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1) 9 errExit("sigprocmask1"); 10 11 /* ... Code that should not be interrupted by SIGINT */ 12 13 /* Restore previous signal mask, unblocking SIGINT */ 14 if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1) 15 errExit("sigprocmask2");
SUSv3 规定,如果有任何等待信号因对 sigprocmask() 的调用而解除了锁定,那么在此调用返回前至少会传递一个信号。换言之,如果解除了对某个等待信号的锁定,那么会立即将该信号传递给进程。
系统将忽略试图阻塞 SIGKILL 和 SIGSTOP 信号的请求。如果试图阻塞这些信号,sigprocmask() 函数既不会予以关注,也不会产生错误。这意味着,可以使用如下代码来阻塞除 SIGKILL 和 SIGSTOP 之外的所有信号:
1 sigfillset(&blockSet); 2 if (sigprocmask(SIG_BLOCK, &blockSet, NULL) == -1) 3 errExit("sigprocmask");
处于等待状态的信号
如果某进程接受了一个该进程正在阻塞的信号,那么会将该信号添加到进程的等待信号集中。当之后解除了对该信号的锁定时,会随之将信号传递给此进程。为了确定进程中处于等待状态的是哪些信号,可以使用 sigpending()
#include <signal.h> int sigpending(sigset_t* set); Returns 0 on success, or -1 on error
sigpending() 系统调用为调用进程返回处于等待状态的信号集,并将其置于 set 指向的 sigset_t 结构中。随后可以使用 sigismember() 函数检查 set。
如果更改了对等待信号的处理器程序,那么当后来解除对信号的锁定时,将根据新的处理器程序来处理信号。这项技术虽然不经常使用,但是还存在一个应用场景,即将对信号的处置置为 SIG_IGN,或者 SIG_DEL(如果信号的默认行为是忽略),从而阻止传递处于等待状态的信号。因此,会将信号从进程的等待信号集中移除,从而不传递该信号。
等待信号:pause()
调用 pause() 将暂停进程的执行,直至信号处理器函数中断该调用为止(或者直至一个未处理信号终止进程为止)。
#include <unistd.h> int pause(void); Always returns -1 with errno set to EINTR
处理信号时,pause() 遭到中断,并总是返回 -1,并将 errno 置为 EINTR。
不对信号进行排队处理
等待信号集只是一个掩码,仅表明一个信号是否发生,而未表明发生的次数。换言之,如果同一信号在阻塞状态下产生多次,那么会将该信号记录在等待信号集中,并在稍后仅传递一次。(标准信号和实时信号之间的差异之一在于,对实时信号进行了排队处理)
signal_functions.h
#ifndef SIGNAL_FUNCTIONS_H #define SIGNAL_FUNCTIONS_H #include <signal.h> #include "tlpi_hdr.h" int printSigMask(FILE *of, const char *msg); int printPendingSigs(FILE *of, const char *msg); void printSigset(FILE* of, const char* prefix, const sigset_t* sigset); #endif
signal_functions.c
1 #define _GNU_SOURCE 2 #include <string.h> 3 #include <signal.h> 4 #include <signal_functions.h> /* Declares functions defined here */ 5 #include "tlpi_hdr.h" 6 7 /* NOTE: All of the following functions employ fprintf(), which is not async-signal-safe. 8 As sunch, these functions are also not async-signal-safe (i.e., beware of 9 indiscriminately calling them from signal handlers).*/ 10 11 void printSigset(FILE* of, const char* prefix, const sigset_t* sigset) 12 { 13 int sig, cnt; 14 cnt = 0; 15 for (sig = 1; sig < NSIG; sig++) { 16 if (sigismember(sigset, sig)) { 17 cnt++; 18 fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig)); 19 } 20 } 21 22 if (cnt == 0) 23 fprintf(of, "%s<empty signal set>\n", prefix); 24 } 25 26 int printSigMask(FILE* of, const char* msg) 27 { 28 sigset_t currMask; 29 if (msg != NULL) 30 fprintf(of, "%s", msg); 31 32 if (sigprocmask(SIG_BLOCK, NULL, &currMask) == -1) 33 return -1; 34 35 printSigset(of, "\t\t", &currMask); 36 return 0; 37 } 38 39 int printPendingSigs(FILE* of, const char* msg) 40 { 41 sigset_t pendingSigs; 42 if (msg != NULL) 43 fprintf(of, "%s", msg); 44 45 if (sigpending(&pendingSigs) == -1) 46 return -1; 47 48 printSigset(of, "\t\t", &pendingSigs); 49 return 0; 50 }
sig_sender.c
1 #include <signal.h> 2 #include "tlpi_hdr.h" 3 4 int main(int argc, char* argv[]) 5 { 6 int numSigs, sig, j; 7 pid_t pid; 8 9 if (argc < 4 || strcmp(argv[1], "--help") == 0) 10 usageErr("%s pid num-sigs sig-num [sig-num-2]\n", argv[0]); 11 12 pid = getLong(argv[1], 0, "PID"); 13 numSigs = getInt(argv[2], GN_GT_O, "num-sigs"); 14 sig = getInt(argv[3], 0, "sig-num"); 15 16 /* Send signals to receiver */ 17 printf("%s: sending signal %d to process %ld %d times\n", argv[0], sig, (long) pid, numSigs); 18 19 for (j = 0; j < numSigs; j++) 20 if (kill(pid, sig) == -1) 21 errExit("kill"); 22 23 /* If a fourth command-line argument was specified, send that signal */ 24 if (argc > 4) 25 if (kill(pid, getInt(argv[4], 0, "sig-num-2")) == -1) 26 errExit("kill"); 27 28 printf("%s: exiting\n", argv[0]); 29 exit(EXIT_SUCCESS); 30 }
sig_receiver.c
1 #define _GNU_SOURCE 2 #include <signal.h> 3 #include "signal_functions.h" 4 #include "tlpi_hdr.h" 5 6 static int sigCnt[NSIG]; /* Counts deliveries of each signal */ 7 static volatile sig_atomic_t gotSigint = 0; /* Set nonzero if SIGINT is delivered */ 8 9 static void handler(int sig) 10 { 11 if (sig == SIGINT) 12 gotSigint = 1; 13 else 14 sigCnt[sig]++; 15 } 16 17 int main(int argc, char* argv[]) 18 { 19 int n, numSecs; 20 sigset_t pendingMask, blockingMask, emptyMask; 21 22 printf("%s: PID is %ld\n", argv[0], (long)getpid()); 23 24 for (n = 1; n < NSIG; n++) /* Same handler for all signals */ 25 (void)signal(n, handler); /* Ignore errors*/ 26 27 /* If a sleep time was specified, temporarily block all signals, 28 sleep (while another process sends us signals), and then 29 display the mask of pending signals and unblock all signals */ 30 if (argc > 1) { 31 numSecs = getInt(argv[1], GN_GT_O, NULL); 32 33 sigfillset(&blockingMask); 34 if (sigprocmask(SIG_SETMASK, &blockingMask, NULL) == -1) 35 errExit("sigprocmask"); 36 37 printf("%s: sleeping for %d seconds\n", argv[0], numSecs); 38 sleep(numSecs); 39 40 if (sigpending(&pendingMask) == -1) 41 errExit("sigpending"); 42 43 printf("%s: pending signals are: \n", argv[0]); 44 printSigset(stdout, "\t\t", &pendingMask); 45 46 sigemptyset(&emptyMask); /* Unblock all signals */ 47 if (sigprocmask(SIG_SETMASK, &emptyMask, NULL) == -1) 48 errExit("sigprocmask"); 49 } 50 51 while (!gotSigint) /* Loop until SIGINT caught */ 52 continue; 53 54 for (n = 1; n < NSIG; n++) /* Display number of signals received */ 55 if (sigCnt[n] != 0) 56 printf("%s: signal %d caught %d time%s\n", argv[0], n, sigCnt[n], (sigCnt[n] == 1) ? "" : "s"); 57 58 exit(EXIT_SUCCESS); 59 }
例如:
在命令行执行
$ ./sig_receiver 60 & [1] 5047 $ ./sig_receiver: PID is 5047 ./sig_receiver: sleeping for 60 seconds $ ./sig_sender 5047 1000000 10 2 ./sig_sender: sending signal 10 to process 5047 1000000 times ./sig_sender: exiting ./sig_receiver: pending signals are: 2 (Interrupt) 10 (User defined signal 1) ./sig_receiver: signal 10 caught 1 time ^C [1]+ Done ./sig_receiver 60
可以看到即使一个信号发送了一百万次,但仅会传递一次给接收者