进程信号 —— 信号的保存
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 设置阻塞信号:
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT); // 把 SIGINT 加入阻塞集合
sigprocmask(SIG_BLOCK, &set, NULL); // 设置阻塞
此后即使 Ctrl+C 发出 SIGINT,信号也不会递达,直到解除阻塞。
2. Pending 位图(未决信号集合)
- 叫做:
pending或signal_pending。 - 类型:也是
sigset_t。 - 作用:表示当前已经产生、但还没递达的信号(记录已经发来了但还没来得及处理的信号)。
每当一个信号产生时,如果它 处于阻塞状态,就会被加入 pending 集合。只有解除阻塞,pending 中的信号才会尝试递达。
这些信号其实已经发给你了,但是因为你之前屏蔽了它们,所以不能马上处理。系统会先把它们存在 pending 队列里,等你不屏蔽了,再一个个处理。就像你正在开会,手机不断收到消息提醒(相当于信号),但你现在不方便看手机(相当于屏蔽)。于是这些消息就先存着,等你开完会再去查看。
3. Handler 指针数组(信号处理函数表)
- 用户空间设置方式:使用
signal()或sigaction()。 - 内核结构体:每个进程有一个叫
sighand_struct的结构体(大小通常为_NSIG),里面有个数组,记录每个信号对应的处理方式。
struct sighand_struct
{
...
struct k_sigaction action[_NSIG]; // 每个信号一个动作函数
...
};
每个数组元素都表示:当信号递达时执行的处理方式(三种)。

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. 代码示例
sigset_t set;
sigemptyset(&set); // set 现在是一个空的信号集,所有位为 0
2. int sigfillset(sigset_t *set);
1. 作用
将所有信号设置为 有效(1),也就是“全信号集”。用于一次性屏蔽所有信号。
2. 代码示例
sigset_t set;
sigfillset(&set); // set 现在包含所有信号(位图全为 1)
3. int sigaddset(sigset_t *set, int signo);
1. 作用
向信号集添加一个信号,使其在集合中 有效(设为 1)。
2. 代码示例
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT); // 将 SIGINT 添加进集合
4. int sigdelset(sigset_t *set, int signo);
1. 作用
从信号集中移除某个信号,使其在集合中 无效(设为 0)。
2. 代码示例
sigset_t set;
sigfillset(&set); // 初始化为所有信号
sigdelset(&set, SIGINT); // 移除 SIGINT
5. int sigismember(const sigset_t *set, int signo);
1. 作用
检测某个信号是否在信号集中(是否为 1)。
2. 返回值
- 存在:返回 1。
- 不存在:返回 0。
- 错误:返回 -1。
3. 代码示例
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
if (sigismember(&set, SIGINT))
{
printf("SIGINT 存在于信号集中\n");
}
else
{
printf("SIGINT 不存在于信号集中\n");
}
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. 函数原型
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
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)
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void myhandler(int signo)
{
cout << "收到信号:" << signo << endl;
}
int main()
{
signal(SIGINT, myhandler); // 设置 SIGINT 信号的处理函数为 myhandler,SIGINT 是 Ctrl+C 发出的中断信号
sigset_t mask, oldmask; // 定义两个信号集:mask:新的信号阻塞集,oldmask:保存原来的信号阻塞集,用于之后恢复
sigemptyset(&mask); // 初始化 mask 为一个空集合(所有信号都未被阻塞)
sigaddset(&mask, SIGINT); // 向 mask 中添加 SIGINT 信号,表示我们想阻塞 SIGINT(Ctrl+C)
sigprocmask(SIG_BLOCK, &mask, &oldmask);// 使用 sigprocmask 设置当前进程的阻塞信号集:SIG_BLOCK(添加),mask(新的阻塞集),oldmask(保存原来的阻塞集)
cout << "SIGINT 被阻塞,按 Ctrl+C 不会触发 handler..." << endl; // 此时 SIGINT 被阻塞,即使按下 Ctrl+C 也不会立即触发信号处理函数
sleep(5); // 程序休眠 5 秒钟,期间 SIGINT 被阻塞
sigprocmask(SIG_SETMASK, &oldmask, nullptr); // 恢复原来的信号阻塞集(解除 SIGINT 的阻塞),SIG_SETMASK:将当前阻塞集完全替换为 mask 中的集合,oldmask:之前保存的阻塞集合
cout << "解除阻塞,SIGINT 可再次递达..." << endl; // 现在 SIGINT 可以正常递达,再次按下 Ctrl+C 就会触发 myhandler
sleep(5); // 再次休眠 5 秒,此时可以接收到 SIGINT 信号
return 0;
}
运行结果就不演示了。mask 和 oldmask 是“信号集合变量”,它们本身没有默认包含任何信号。要用 sigaddset() 手动添加想阻塞的信号,用 sigprocmask() 告诉系统要怎么处理这些信号。
2. sigpending 函数 —— 获取当前进程未决信号集
1. 功能
用于 获取当前进程的未决信号集,也就是哪些信号已经产生,但 尚未递达(因为它们被阻塞了)。通常配合 sigprocmask 使用:我们阻塞一个信号,然后用 sigpending 检查它是否 pending。
2. 函数原型
#include <signal.h>
int sigpending(sigset_t *set);
3. 参数详解
sigset_t *set: 输出参数,保存当前进程的未决信号集合。
4. 返回值
- 成功返回
0。 - 失败返回
-1并设置errno。
5. 代码示例:阻塞 SIGINT,并检查它是否 pending
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
int main()
{
sigset_t block_set, pending_set; // 定义两个信号集合
sigemptyset(&block_set); // 初始化 block_set 为一个空集合(所有信号都不阻塞)
sigaddset(&block_set, SIGINT); // 向 block_set 中添加 SIGINT 信号(Ctrl+C),表示我们想阻塞这个信号
// 使用 sigprocmask 函数设置当前进程的信号屏蔽字(block 集合),SIG_BLOCK 表示将 block_set 中的信号添加到当前阻塞集合中(叠加),第三个参数为 nullptr,表示不保存原来的阻塞集合
sigprocmask(SIG_BLOCK, &block_set, nullptr);
cout << "请在 5 秒内按 Ctrl+C(不会立刻触发 handler)..." << endl; // 在 5 秒内按下 Ctrl+C,此时 SIGINT 会被阻塞,进入 pending 状态
sleep(5); // 程序暂停 5 秒,等待按下 Ctrl+C
sigpending(&pending_set); // 获取当前进程的“未决信号集合”,pending_set 保存当前所有“已产生但未处理”的信号
if (sigismember(&pending_set, SIGINT)) // 检查 SIGINT 是否在 pending_set 中
{
cout << "SIGINT 当前处于 pending 状态。" << endl;// 如果 SIGINT 在 pending 集合中,说明它已经产生但被阻塞了
}
else
{
cout << "SIGINT 没有 pending。" << endl; // 如果 SIGINT 不在 pending 集合中,说明它没被发送,或已经被处理
}
sigprocmask(SIG_UNBLOCK, &block_set, nullptr); // 解除对 SIGINT 的阻塞,让它可以正常递达
sleep(2); // 再等 2 秒,如果之前按过 Ctrl+C,此时 SIGINT 会递达并触发默认处理(终止程序)
return 0;
}
3. 小实验
验证:当一个信号(如 2 号信号 SIGINT)被阻塞时,即使被发送,也不会递达,而是进入 pending 状态,直到解除阻塞才会递达。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
using namespace std;
void printPending(sigset_t *pending)
{
int i = 1;
for (i = 31; i >= 1; i--)
{
if (sigismember(pending, i)) // 检查信号 i 是否在 pending 集合中
{
printf("1"); // 在 pending 集合中
}
else
{
printf("0"); // 不在 pending 集合中
}
}
printf("\n");
}
int main()
{
sigset_t set, oset;
sigemptyset(&set);
sigemptyset(&oset);
sigaddset(&set, 2); // 向 set 中添加 2 号信号(SIGINT,即 Ctrl+C)
sigprocmask(SIG_SETMASK, &set, &oset);
sigset_t pending; // 定义 pending 信号集,用于后续获取当前 pending 的信号
sigemptyset(&pending);
while (1) // 无限循环,持续检测 pending 信号集
{
sigpending(&pending); // 获取当前 pending 信号集
printPending(&pending); // 打印 pending 信号位图(1 表示 pending,0 表示未 pending)
sleep(1); // 每隔 1 秒检测一次
}
return 0;
}
- 运行程序 ,它会阻塞
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。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
using namespace std;
// 打印 pending 位图(31 位),1 表示 pending,0 表示未 pending
void printPending(sigset_t *pending)
{
for (int i = 31; i >= 1; i--)
{
if (sigismember(pending, i))
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
}
int main()
{
sigset_t block_set, old_mask;
sigemptyset(&block_set);
sigemptyset(&old_mask);
// 阻塞 1~31 号信号
for (int i = 1; i <= 31; i++)
{
sigaddset(&block_set, i);
}
// 设置阻塞信号集(屏蔽所有 1~31 号信号)
sigprocmask(SIG_SETMASK, &block_set, &old_mask);
printf("已屏蔽 1~31 号信号,现在你可以发送信号了。\n");
printf("例如:kill -1 PID、kill -2 PID ... kill -31 PID\n");
sigset_t pending;
while (1)
{
sigemptyset(&pending);
sigpending(&pending); // 获取当前 pending 信号集合
printPending(&pending);
sleep(1);
}
return 0;
}
# 依次发送信号:
kill -1 12345
kill -2 12345
kill -3 12345
...
kill -31 12345
# 或者写个脚本自动发送:for i in {1..31}; do kill -$i <实际PID>; sleep 1; done
# 查看进程 PID:ps aux | grep -E 'COMMAND|test2' | grep -v grep(示例)
运行结果示例:
