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

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

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

时间: 2024-08-06 07:54:55

信号集 / 信号掩码(阻塞信号传递)的相关文章

2信号处理之:信号产生原因,进程处理信号行为,信号集处理函数,PCB的信号集,sigprocmask()和sigpending(),信号捕捉设定,sigaction,C标准库信号处理函数,可重入函数,

 1信号产生原因 2.进程处理信号行为 manpage里信号3中处理方式: SIG_IGN SIG_DFL                                            默认Term动作 a signal handling function 进程处理信号 A默认处理动作 term   中断 core    core(调试的时候产生) gcc –g file.c ulimit –c 1024 gdb a.out core ign      忽略 stop     停止

linux 信号集操作

信号掩码 – 被阻塞的信号集 每个进程都有一个用来描述哪些信号传送来将被阻塞的信号集,如果某种信号在某个进程的阻塞信号集中,则传送到该进程的此种信号将会被阻塞.当前被进程阻塞的信号集也叫信号掩码,类型为sigset_t.每个进程都有自己的信号掩码,且创建子进程时,子进程会继承父进程的信号掩码. 信号阻塞和忽略的区别 阻塞的概念与忽略信号是不同的:操作系统在信号被进程解除阻塞之前不会将信号传递出去,被阻塞的信号也不会影响进程的行为,信号只是暂时被阻止传递:当进程忽略一个信号时,信号会被传递出去,但

信号集

信号集的函数: 类型:sigset_t 类型 #include <signal.h> /**************************** *功能:把一个信号集的内容清空 *参数:sigset_t *set:是一个信号集,sigset_t的类型一定是可以容纳所有的信号的个数 *返回值:成功返回0,失败返回-1 * ************************/ int sigemptyset(sigset_t *set); /****************************

信号的屏蔽,信号集

1.信号集 POSIX标准定义了数据类型sigset_t #include <signal.h> int sigemptyset(sigset_t *set); 初始化一个信号集,使其不包括任何信号 int sigfillset(sigset_t *set); 用来初始化一个信号集,使其包括所有信号 int sigaddset(sigset_t *set, int signum); 用来向set指定的信号集中添加由signum指定的信号 int sigdelset(sigset_t *set,

linux 信号集 二 sigaction

sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作). 他是POSIX的信号接口,而signal()是标准C的信号接口(如果程序必须在非POSIX系统上运行,那么就应该使用这个接口) 给信号signum设置新的信号处理函数act, 同时保留该信号原有的信号处理函数oldact int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact);

linux 信号集

[sigprocmask系统调用]功能描述:设定对信号屏蔽集内的信号的处理方式(阻塞或不阻塞).用法:#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);参数:how:用于指定信号修改的方式,可能选择有三种 SIG_BLOCK //加入信号到进程屏蔽.SIG_UNBLOCK //从进程屏蔽里将信号删除.SIG_SETMASK //将set的值设定为新的进程屏蔽. set:为指向信号

APUE学习笔记——10.11~10.13 信号集、信号屏蔽字、未决信号

如有转载,请注明出处:Windeal专栏 首先简述下几个概念的关系: 我们通过信号集建立信号屏蔽字,使得信号发生阻塞,被阻塞的信号即未决信号. 信号集: 信号集:其实就是一系列的信号.用sigset_t set表示. 数据类型:sigset_t 类似于整型(位数可能超过整型,因而不能用整型表示). 我们一般在sigprocmask()等函数中使用信号集,用于创建一系列进程要阻塞的信号,告诉内核不允许这些信号发生. 几个关于信号集的函数: #include <signal.h> int sige

signal函数、sigaction函数及信号集(sigemptyset,sigaddset)操作函数

信号是与一定的进程相联系的.也就是说,一个进程可以决定在进程中对哪些信号进行什 么样的处理.例如,一个进程可以忽略某些信号而只处理其他一些信号:另外,一个进程还可以选择如何处理信号.总之,这些总与特定的进程相联系的.因此,首 先要建立其信号和进程的对应关系,这就是信号的安装登记. Linux 主要有两个函数实现信号的安装登记:signal和sigaction.其中signal在系统调用的基础上实现,是库函数.它只有两个参数,不支持信号 传递信息,主要是用于前32个非实时信号的安装:而sigact

pthread访问调用信号线程的掩码

pthread_sigmask 语法 int pthread_sigmask(int how, const sigset_t *new, sigset_t *old); #include <pthread.h> #include <signal.h> int ret; sigset_t old, new; ret = pthread_sigmask(SIG_SETMASK, &new, &old); /* set new mask */ ret = pthread_