信号集 / 信号掩码(阻塞信号传递)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了信号集 / 信号掩码(阻塞信号传递)相关的知识,希望对你有一定的参考价值。

【摘自《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

可以看到即使一个信号发送了一百万次,但仅会传递一次给接收者

以上是关于信号集 / 信号掩码(阻塞信号传递)的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp Linux的信号集操作,操作信号屏蔽字(阻塞信号集),读取未决信号集

Linux之信号阻塞集

信号集和信号集操作函数信号屏蔽集设置和演示

Linux_进程信号(进程信号生命周期_Core Dump调试_进程信号捕捉_系统调用向进程发送信号_阻塞信号_信号集函数_处理信号内核态与用户态_C语言volatile关键字_SIGCHLD信号)(

进程信号——阻塞与捕捉信号

异步通信之 信号