20.9 信号集和信号的处理
说明
20 信号 讲了信号的基本概念、列举了一部分信号,并且说明了发送信号的方式。这一节主要是讲和信号处理相关的 API。
信号集
信号集对应类型 sigset_t
,操作它的函数都是以其指针为参数的。相关的函数有:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
必须用 sigemptyset
或者 sigfillset
对 sigset_t
类型的数据完成初始化,直接对其赋值 0 会有可移植性的问题。
GNU C 库还实现了三个函数:sigandset
/ sigorset
/ sigisemptyset
。
不过,这些函数都没有提供遍历信号的方法。我们可以用 for
循环来遍历元素,这需要用到 NSIG
,它表示信号的最大编号 + 1。可能需要定义 BSD_SOURCE
、_SVID_SOURCE
或者 _GNU_SOURCE
之一来获取它的定义。
sigprocmask
:用信号掩码阻塞信号的传递
信号掩码能够将对应的信号暂时屏蔽掉,等掩码位移除之后再生效。这里说的阻塞并不是阻塞线程,而是阻塞信号。实际上,信号掩码属于线程属性,每个线程都可以有自己的信号掩码。
#include <signal.h>
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *_Nullable restrict set,
sigset_t *_Nullable restrict oldset);
sigprocmask
系统调用就能设置当前线程的掩码,并将旧的掩码保存到 oldset
中(如果它非空)。其中 how 参数可以设置为以下值:
SIG_BLOCK
表示将给定的信号阻塞,也就是和当前的线程掩码取并集(union)。SIG_UNBLOCK
表示不阻塞给定的信号,也就是当前线程掩码 - 给定的掩码(差集)。SIG_SETMASK
表示将当前的掩码赋值给线程掩码。
如果用 sigprocmask
解除了某些信号的阻塞,并且当前已经有这些类型的信号,则信号会在 sigprocmask
返回前发出。另外,尝试阻塞 SIGKILL
和 SIGSTOP
是不起作用的(因为这两个信号是无论无何都无法阻塞的),也不会报错。
Note
实时信号时可以排队的,标准信号不能。
Caution
实际上还有一条说明:The use of sigprocmask()
is unspecified in a multithreaded process; see pthread_sigmask(3)
. 也就是说 sigprocmask()
是不能用在多线程环境中的。
sigpending
:获取当前正处于等待状态的信号
线程(如果系统不支持多线程,则是进程)如果被设置了信号掩码,则信号产生时会被加入进程的等待信号中,既不会立即发送,也不会被直接丢弃。不过,处于等待状态的信号会被合并,因为内核没有追溯信号的数量,信号只有存在和不存在两种状态。即便是某个进程没有通过设置掩码来阻塞信号,其他进程向它短时间内发送大量同一信号,这些信号也会在进程来不及处理时合并,因此进程实际上处理信号的次数可能少于发送信号的次数。
#include <signal.h>
int sigpending(sigset_t *set);
Man 手册中:
sigpending()
returns the set of signals that are pending for delivery to the calling thread (i.e., the signals which have been raised while blocked). The mask of pending signals is returned in set.
sigaction
:改变信号处理方式
#include <signal.h>
int sigaction(int signum,
const struct sigaction *_Nullable restrict act,
struct sigaction *_Nullable restrict oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sigaction
既是函数名,又是类型名,由于结构体使用时需要在前面加上 struct
关键字,所以它们可以被编译器区分。和其他几个函数不一样,这个函数是进程级别的!因此,任何一个线程都可能接收信号并进行信号处理。
Tip
如果用的是 typedef
来定义类型别名,那么类型名就会和函数名冲突了。在 C++ 中类型名和函数名是可以重名的,即便是加了 extern "C"
也不会冲突,这是因为 C++ 语法分析的方式不同。
参数解释:
sa_handler
和 sa_sigaction
可能是用 union 的方式实现的,所以只能对其中之一赋值。 sa_handler
可以是用户提供的函数,也可以是 SIG_IGN
或者 SIG_DFL
,前者表示忽略信号,后者表示默认的处理方式。当且仅当 sa_handler
是用户提供的处理器时,才会对 sa_mask 和 sa_flags
字段做出处理。
sa_restorer
由相关函数使用,不由用户直接使用。它被用来在信号处理器返回时用系统调用 sigreturn
恢复进程的执行上下文。
sa_mask
用来表示信号处理器执行时忽略的信号。信号处理器执行前会把 sa_mask
中的信号短暂地加入到线程的信号掩码中,在执行结束后又将信号掩码恢复。另外,引发信号处理的具体信号会被加入到信号掩码中,这保证了如果同一个信号第二次抵达,信号处理程序不会递归中断自己。
sa_flags
则是一组标志的按位或,会修改 sigaction
函数的行为。
pause
:等待信号的发生
#include <unistd.h>
int pause(void);
阻塞进程(或者线程),直到信号被捕获,或者因为未处理信号导致程序退出。该函数返回值总是 -1,且总是将 errno
设置成 EINTR
。