037 进程信号 —— 信号的保存

037 进程信号 —— 信号的保存
小米里的大麦进程信号 —— 信号的保存
1. 信号的其他相关概念
概念 | 含义 | 例子 |
---|---|---|
信号产生(Generate) | 内核决定给进程发送一个信号 | 比如你按了 Ctrl+C ,系统决定给你的程序发一个 SIGINT 信号,就像同事敲了你的门说“有事” |
信号未决(Pending) | 信号已经产生,但还没被处理,只能排队等着 | 你正在开会,同事敲了门,但你没理他,他的请求被记下来了,等你有空再处理 |
信号阻塞(Blocked) | 你设置了“我暂时不想处理这些信号” | 你提前设置了“开会期间不接电话”,这些信号来了也只能排队等待,不会立刻打断你 |
信号递达(Delivery) | 信号从 pending 状态变为被处理的状态 | 你开完会,系统发现有一个 SIGINT 在排队,于是开始处理它,触发你设置的处理函数 |
信号处理(Handler) | 你决定怎么处理这个信号(默认、忽略、自定义函数) | 你决定怎么处理这个信号: 1. 默认处理(系统帮你处理) 2. 忽略处理(假装没发生) 3. 自定义函数(你写好逻辑来处理) |
阻塞 ≠ 忽略 ≠ 未决:
- 阻塞 是控制递达的时机,信号仍然会记录下来(进入 pending)。
- 忽略 是告诉内核「收到信号后什么都不做」,但是信号 必须先递达。
- 未决 是信号已经来了,但因为被阻塞,暂时不能处理,只能排队。
- 阻塞 是你设置了“不想被打扰”。
- 未决 是“打扰已经来了,但你暂时不能处理”。
- 忽略 是“即使打扰来了,你也假装没发生”。
信号集 = 一堆信号的集合,用来告诉操作系统:“我现在想屏蔽哪些信号?” 或 “我正在等待哪些信号?” 或 “我目前还没处理的信号有哪些?”
2. 信号处理模型的三大组成(内核中的表示)
1. Block 位图(阻塞信号集合)
- 叫做:
blocked
或signal_blocked
。 - 类型:
sigset_t
(实际是一个位图 bitset) - 作用:表示进程 当前阻塞了哪些信号(告诉系统:“我现在不想处理这些信号”)。
- 阻塞意味着:这些信号虽然可以被内核记录为 pending,但不能递达(这个集合里记录的是进程当前暂时屏蔽掉的信号。被屏蔽的信号即使发来了,也不会立刻处理,只能先记下来,等你不屏蔽了再处理)。
示例:用 sigprocmask
设置阻塞信号:
1 | sigset_t set; |
此后即使 Ctrl+C 发出 SIGINT,信号也不会递达,直到解除阻塞。
2. Pending 位图(未决信号集合)
- 叫做:
pending
或signal_pending
。 - 类型:也是
sigset_t
。 - 作用:表示当前已经产生、但还没递达的信号(记录已经发来了但还没来得及处理的信号)。
每当一个信号产生时,如果它 处于阻塞状态,就会被加入 pending 集合。只有解除阻塞,pending 中的信号才会尝试递达。
这些信号其实已经发给你了,但是因为你之前屏蔽了它们,所以不能马上处理。系统会先把它们存在 pending 队列里,等你不屏蔽了,再一个个处理。就像你正在开会,手机不断收到消息提醒(相当于信号),但你现在不方便看手机(相当于屏蔽)。于是这些消息就先存着,等你开完会再去查看。
3. Handler 指针数组(信号处理函数表)
- 用户空间设置方式:使用
signal()
或sigaction()
。 - 内核结构体:每个进程有一个叫
sighand_struct
的结构体(大小通常为_NSIG
),里面有个数组,记录每个信号对应的处理方式。
1 | struct sighand_struct |
每个数组元素都表示:当信号递达时执行的处理方式(三种)。
3. 什么是 sigset_t
?它为什么重要?
1. 本质
sigset_t
是一个用于表示信号集合的结构体类型。内部实现是一个 位图,每一个 bit 表示一个信号编号的状态:
- 第
n
位为1
→ 表示 第 n 个信号有效 - 第
n
位为0
→ 表示 第 n 个信号无效
2. 用途
sigset_t
可以用于两种语义,被用于以下 API:sigprocmask
、sigpending
、pthread_sigmask
等:
- 📦 阻塞信号集(Signal Mask)—— 表示哪些信号被阻塞。
- 🔔 未决信号集(Pending Set)—— 表示哪些信号已产生但未递达。
4. 信号集操作函数讲解
这里的 5 个函数的参数都是 指针类型 ,他们需要修改传入的
sigset_t
变量本身,所以下面的 & 是 取地址 而非位运算!他们的 头文件也都是#include <signal.h>
。
1. int sigemptyset(sigset_t *set);
1. 作用:
清空信号集,使所有信号都 无效(0)。适用于初始化,创建一个 空集合。
2. 代码示例
1 | sigset_t set; |
2. int sigfillset(sigset_t *set);
1. 作用
将所有信号设置为 有效(1),也就是“全信号集”。用于一次性屏蔽所有信号。
2. 代码示例
1 | sigset_t set; |
3. int sigaddset(sigset_t *set, int signo);
1. 作用
向信号集添加一个信号,使其在集合中 有效(设为 1)。
2. 代码示例
1 | sigset_t set; |
4. int sigdelset(sigset_t *set, int signo);
1. 作用
从信号集中移除某个信号,使其在集合中 无效(设为 0)。
2. 代码示例
1 | sigset_t set; |
5. int sigismember(const sigset_t *set, int signo);
1. 作用
检测某个信号是否在信号集中(是否为 1)。
2. 返回值
- 存在:返回 1。
- 不存在:返回 0。
- 错误:返回 -1。
3. 代码示例
1 | sigset_t set; |
6. 小结
函数名 | 功能 | 类比 |
---|---|---|
sigemptyset |
清空集合 | 清空列表 |
sigfillset |
加入所有信号 | 填满列表 |
sigaddset |
添加某信号进集合 | 插入元素 |
sigdelset |
移除某信号出集合 | 删除元素 |
sigismember |
检查信号是否存在集合中 | 查询元素 |
延伸:信号集 ≠ 信号队列
sigset_t
是位图,不记录信号发生次数。- 即便一个信号连续产生 10 次,只要它处于未决状态,pending 位图中就只有一个 bit 为 1。
- 所以,信号不具备计数能力。
- 多次产生的同一个信号,只会保留一次,除非是实时信号。
5. sigprocmask
和 sigpending
函数
sigprocmask
和 sigpending
是信号机制中 非常核心的两个系统调用接口,用于操作进程的信号阻塞状态和查看未决信号。
函数名 | 作用 | 常用用途 |
---|---|---|
sigprocmask |
设置 / 修改 / 查询阻塞信号集 | 控制哪些信号不被递达 |
sigpending |
查询未决信号集 | 查看哪些信号已产生但尚未递达 |
1. sigprocmask
函数 —— 修改/获取进程的信号屏蔽字(阻塞信号集)
1. 功能
用于 设置 / 修改 / 查询 当前进程的信号屏蔽字(设置“我现在不想处理哪些信号”的函数),即“阻塞信号集合”。信号屏蔽字就是 task_struct.blocked
,通过 sigprocmask
修改它可以:
- 设置新的阻塞信号集合。
- 添加或删除某些信号的阻塞状态。
- 查询当前阻塞了哪些信号。
2. 函数原型
1 |
|
3. 参数详解
参数名 | 类型 | 含义 |
---|---|---|
how |
int |
操作类型(如下表),你要怎么改?是加一些?删一些?还是? |
set |
sigset_t * |
要设置的新信号集(也就是目标集合),你这次想“屏蔽哪些信号” |
oldset |
sigset_t * |
可选,保存原来的屏蔽集(先记下当前的“勿扰清单”,以后还能恢复它) |
how
参数可选值:
值 | 解释 | 示例 |
---|---|---|
SIG_BLOCK |
把 set 中的信号加入当前屏蔽集(叠加) |
原来屏蔽 A,现在 set 是 B → 屏蔽 A+B |
SIG_UNBLOCK |
从当前屏蔽集中去掉 set 中的信号 |
原来屏蔽 A+B,set 是 B → 现在只屏蔽 A |
SIG_SETMASK |
直接用 set 替换整个屏蔽集 |
原来屏蔽 A+B,现在 set 是 C → 现在屏蔽 C |
4. 返回值
- 成功返回
0
- 失败返回
-1
,并设置errno
5. 代码示例:阻塞和解除阻塞 SIGINT(Ctrl+C)
1 |
|
运行结果就不演示了。mask 和 oldmask 是“信号集合变量”,它们本身没有默认包含任何信号。要用 sigaddset()
手动添加想阻塞的信号,用 sigprocmask()
告诉系统要怎么处理这些信号。
2. sigpending
函数 —— 获取当前进程未决信号集
1. 功能
用于 获取当前进程的未决信号集,也就是哪些信号已经产生,但 尚未递达(因为它们被阻塞了)。通常配合 sigprocmask
使用:我们阻塞一个信号,然后用 sigpending
检查它是否 pending。
2. 函数原型
1 |
|
3. 参数详解
sigset_t *set
: 输出参数,保存当前进程的未决信号集合。
4. 返回值
- 成功返回
0
。 - 失败返回
-1
并设置errno
。
5. 代码示例:阻塞 SIGINT,并检查它是否 pending
1 |
|
3. 小实验
验证:当一个信号(如 2 号信号 SIGINT)被阻塞时,即使被发送,也不会递达,而是进入 pending 状态,直到解除阻塞才会递达。
1 |
|
- 运行程序 ,它会阻塞
SIGINT
(2 号信号,即 Ctrl+C)。 - 发送 SIGINT 信号 (使用
kill -2 PID
或 Ctrl+C)。使用ps aux | grep -E 'COMMAND|test1' | grep -v grep
查找 PID。 - 观察输出 :是否在 pending 位图中看到
1
。 - 验证信号确实被阻塞 ,程序不会退出。
运行结果示例:
让 1~31 号信号全部 pending,观察从全 0 到全 1 的变化过程:
- 屏蔽所有 1~31 号信号 (即全部阻塞)。
- 发送多个信号(1~31)。
- 观察 pending 位图从全 0 变成全 1。
1 |
|
1 | # 依次发送信号: |
运行结果示例: