常见信号术语
- 信号递达(Delivery):实际执行信号的处理动作
- 信号未决(Pending):信号从产生到递达的中间状态
- 信号阻塞(Block):进程可以选择阻塞某一个信号,被阻塞的的信号保持在未决状态,直到接触阻塞才会被递达。
- 信号忽略:信号在递达之后的一种不作为。与阻塞不同,被阻塞的信号没有被递达。
- 捕获信号:进程通过自定义信号处理程序来处理信号,而不是使用系统的默认动作
信号在内核中的表示
对于信号的学习,我们将其分为三个部分:产生信号、接收信号、处理信号。现在我们已经知道了信号是如何产生的,那我们的进程又是如何接收信号的呢?换句话说,一个进程是如何知道操作系统给它发信号了呢?
Pending表
其实在进程的PCB里面有一个信号位图(Signal Bitmap),用于表示有哪些发送给该进程但是还没有被处理的信号。每一个位都表示一种信号,如果有信号发送给该进程,那么这个位图对应的信号位就会置为1。假如给进程发送一个3号信号,那么这个位图的第3位就会置为1。因为信号的发送与进程实际处理之间是异步的,所以发送信号之后进程可能不会立即处理这个信号。
这个信号位图也被称为Pending(未决)表
有了Pending表,进程就能知道接收了哪些信号并准备处理信号,而一个信号是否要被处理,则需要观察该信号是否被阻塞。此时需要查看进程的Block表的内容。(处理信号相当于和响应信号)
Block表
同样的,PCB里面还有一个位图表示被进程阻塞的信号集。其每一个比特位都表示一个信号编号,如果是1则表示该对应信号被阻塞,即使信号到达,进程也不会处理。直到信号从该Block表中移除。Block表起到的作用实际上就是一个信号掩码(sig mask)。
sigset_t
观察上面图片,信号的未决表和阻塞表都是位图结构,且每一个比特位的位置都是表示信号的编号。于是我们可以统一用一个数据类型sigset_t(位图)来储存pending表和Block表。这个类型可以表示每个信号的有效或者无效状态。在阻塞信号集中,有效表示阻塞,在pending信号集中,有效表示未决。
我们可以通过signal函数来自定义一个信号的处理动作,实际上就是修改handler数组中元素内容·
信号集操作函数
操作系统提供了一些函数来管理信号集(sigset_t,包括Block和pending表),以下是常见的信号集操作函数(都包含在signal.h
头文件中):
sigemptyset
功能:初始化一个空的信号集,即将信号集中所有信号清楚,也就是让信号集中的每一个比特位清0。
原型:
int sigemptyset(sigset_t *set);
- 将
set
指向的信号集清空 - 成功返回0,否则返回-1.
sigfillset
功能:初始化一个满的信号集,跟sigemptyset函数相反,让信号集中每一个比特位都置为1。
原型:
int sigfillset(sigset_t *set);
- 将
set
指向的信号集填满
- 成功返回0,否则-1.
sigaddset
功能:从信号集中添加一个指定的信号。
原型:
int sigaddset(sigset_t *set, int signum);
- 给
set
指向的信号集中添加一个signum
信号 - 成功放回0,否则-1.
sigdelset
从信号集中删除一个指定的信号
原型:
int sigdelset(sigset_t* set,int signum);
- 从
set
指向的信号集中删除一个signum
信号 - 成功返回0,失败则返回-1.
sigismember
功能:检查一个指定的信号是否在信号集中
原型:
int sigismember(const sigset_t *set,int signum);
- 如果
set
指向的信号集中有signum
信号就返回1,不在返回0,查看失败返回-1。
sigprocmask
功能:检查或者修改信号屏蔽字(阻塞信号集)。用来阻塞或者解阻信号。
跟上面的sigaddset和sigdelset函数不一样的是,sigprocmask可以修改进程的Block表。
原型:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how
:指定操作类型。以下是常见的操作类型:
SIG_BLOCK
:将set
指向的信号集中的信号添加到阻塞信号集中SIG_UNBLOCK
:从阻塞信号集中移除set
指向的信号集中的信号SIG_SETMASK
:将set
指向的信号集设置为新的阻塞信号集
se
t指向一个sigset_t
类型的对象,用于修改阻塞信号集。如果该参数为NULL
,则表示不修改阻塞信号集。oldset
:指向一个sigset_t
类型的对象,用于存储被set修改之前的阻塞信号集。- 成功返回0,失败返回-1.
sigpending
功能:获取当前进程的未决信号集(pending表)
原型:
int sigpending(sigset_t *set);
- 将
set
指向的信号集重置为当前进程的未决信号集 - 成功返回0,否则-1.
程序测试
为了更好的理解信号未决和信号阻塞。现在给出一个代码测试,观察其运行结果:
#include <iostream> #include <signal.h> using namespace std; void PrintSet(sigset_t *set) { for (int i = 31; i >= 1; i--) { if (sigismember(set, i)) { putchar('1'); } else { putchar('0'); } } puts(""); } int main() { sigset_t s, p; sigemptyset(&s);//初始化信号集 sigaddset(&s, SIGINT);//添加2号 sigprocmask(SIG_BLOCK, &s, NULL); while (1) { sigpending(&s); PrintSet(&s); sleep(1); } return 0; }
代码中阻塞了信号2(SIGINT),也就是我们ctrl+c发出的信号。在接收到ctrl+c信号时,观察其Pending表的内容。
我们看到,按下ctrl+c后程序没有被终止,说明这个信号确实被阻塞了,然后未决表显示该信号一直出于未决状态,没有被递达。