常见的信号术语
信号递达(Delivery):
- 信号实际被执行处理的过程;(当一个信号被递达给进程时,该信号的处理动作已经开始执行实际执行信号的处理动作);
信号未决(Pending):
- 信号从产生到递达的中间状态;(信号已经生成但尚未被递达的状态。在此期间,信号处于阻塞状态,直到它被递达给目标进程)。
信号阻塞(Block):
- 进程可以选择阻塞某个信号;(即使信号已经生成,也会保持在未决状态,不会被递达给进程。信号只有在解除阻塞后才能被递达)。
信号忽略:
- 信号被递达后,进程选择不做任何响应。不同于信号阻塞,被忽略的信号已经完成了递达,只是进程对其没有任何反应。
捕获信号:
- 进程通过自定义信号处理程序来处理信号,而不是使用系统的默认动作;
信号在内核中的表示
在上篇中介绍了信号是由位图保存的,信号如何产生的;
信号是用户,操作系统,进程交互用的;而进程是如何知道操作系统给它发了信号呢;
信号标志位
- 阻塞 (block):如果设置,该信号会阻塞,不会被递达给进程;
- 未决 (pending):如果设置,表示该信号已生成但未处理;
信号通过三种结构来表示和管理,这些结构存储在进程的task_struct中;
- Pending 位图:表示哪些信号已经生成但尚未被递达或处理;(跟踪信号的未决状态)
- Block 位图:记录了哪些信号被当前进程所阻塞。控制信号是否可以被递达;
- Handler 函数指针:指向一个函数指针,用于指定当信号递达时要执行的处理动作;
Pending表
- 在信号位图(Signal Bitmap)中,每个比特位的位置表示某个信号,比特位的内容代表是否收到该信号(信号是否未决);如果有信号发送给该进程,那么这个位图对应的信号位会置为1,没有就是0;
- 假如给进程发送2号信号,那么该位图的第3位会置位1,但不会立即处理该信号,会在合适的时候处理,这个合适的时候是进程从内核态返回到用户态的时候进行处理;
- Pending表的存在让进程知道接受了哪些信号并准备处理信号;而信号是否要被处理,还要查看该信号是否被阻塞,此时需要查看进程中Block表的内容;
Block表
进程用一张位图表来表示被进程阻塞的信号集;每个比特位的位置代表着某个信号编号,比特位的内容代表着该信号是否被阻塞;内容为1表示对该信号进行阻塞,为0表示没有对该信号进行阻塞。第一个比特位位置代表 1号信号,以此类推(位图结构)
有了上面两张位图表,进程就能知道接受了哪些信号,信号是否阻塞(不需要处理),而信号也有需要处理的;这个就要看handler表;
handler表
进程中这张handler表与上面两张位图表有所不同,这是函数指针数组(sighandler_t handler[32]);数组的下标是信号的编号,数组的内容是函数指针(该信号的处理函数);处理函数(处理动作)包括默认,忽略以及自定义;
- 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。
- block、pending和handler这三张表的每一个位置是一一对应的;
- 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,1号信号SIGHUP 未阻塞也未产生过(pending位图对应信号位置标志位为0,block位图对应信号位置标志位为0),当它递达时执行默认处理动作(SIG_DFL)。
- 对于2号信号SIGINT,该信号产生过(pending位图对应信号位置标志位为1,处于未决),但正在被阻塞(block位图对应信号位置标志位为1),所以暂时不能递达。虽然它的处理动作是忽略(SIG_IGN),但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
- 3号信号SIGQUIT 未产生过(pending位图对应信号位置标志位为0),该信号正在被阻塞(block位图对应信号位置标志位为1),一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
总结:
一个信号哪怕没有被产生,也不影响该信号被阻塞;
POSIX.1标准
允许系统在信号解除阻塞之前,递送该信号一次或多次给进程。
如果在进程接触对某信号的阻塞之前,该信号产生过多次,该如何处理❓
常规信号(普通信号):
如果一个常规信号在被解除阻塞之前产生了多次,那么仅会递送一次该信号给进程。
实时信号:
实时信号在被解除阻塞之前产生的多次可以被依次放入一个队列中等待递送。
sigset_t
由于block和pending 位图是内核内部的数据结构,用户程序无法直接访问和修改它们,OS也不能提供十几个参数的函数给用户使用吧,所以就有了 sigset_t。为了能够修改这些信号集,用户程序需要通过系统调用来进行操作:sigset_t类型的数据结构让用户可以方便地设置和查询信号集的状态;
- 在block,pending位图中,比特位非0即1,只表示信号是否产生;不记录产生了多少次;
- 未决和阻塞标志可以用相同的数据类型 sigset_t 来存储(在用户层);sigset_t 称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。
在阻塞信号集中 :
“有效”状态表示信号被阻塞,“无效”状态表示信号未被阻塞。
在未决信号集:
“有效”状态表示信号处于未决状态,“无效”状态表示信号没有被生成或已被处理。
信号集操作函数
操作系统提供了一些函数来管理信号集(sigset_t,包括Block和pending表),以下是常见的信号集操作函数(都包含在signal.h头文件中):
sigemptyset
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指向的信号集设置为新的阻塞信号集
set:指向一个sigset_t类型的对象,用于修改阻塞信号集。如果该参数为NULL,则表示不修改阻塞信号集。
oldset:指向一个sigset_t类型的对象,用于存储被set修改之前的阻塞信号集。
成功返回0,失败返回-1.
sigpending
获取当前进程的未决信号集(pending表)
int sigpending(sigset_t *set);
- 将set指向的信号集重置为当前进程的未决信号集
- 成功返回0,否则-1.
信号集实验
实验步骤如下:
#include <signal.h>
#include <unistd.h>
// 定义一个函数来打印信号集的内容
void PrintSet(sigset_t *set)
{
// 循环遍历信号集中的每个信号位
for (int i = 31; i >= 1; i--) // 注意:这里从31开始到1结束是不正确的,因为SIGRTMIN是1, SIGRTMAX是64
{
// 如果信号集中的信号i被设置,则输出'1'
if (sigismember(set, i))
{
putchar('1');
}
// 否则输出'0'
else
{
putchar('0');
}
}
// 输出换行符
puts("");
}
int main()
{
sigset_t s, p;
// 初始化信号集s为空
sigemptyset(&s);
// 将 SIGINT (通常为 2) 添加到信号集s中
sigaddset(&s, SIGINT);
// 设置信号屏蔽字,将信号集s中的信号设置为阻塞状态
// 这意味着在此之后,SIGINT将不会被进程接收
sigprocmask(SIG_BLOCK, &s, NULL);
// 主循环
while (1)
{
// 查询当前进程的未决信号集,并将其存储在p中
sigpending(&p);
// 打印未决信号集p的内容
PrintSet(&p);
// 让进程休眠一秒
sleep(1);
}
return 0;
}
按下ctrl+c后程序没有被终止,说明这个信号确实被阻塞了,然后未决表显示该信号一直出于未决状态,没有被递达