Linux 信号控制
目录
一、背景:为什么要研究信号屏蔽与未决信号?
二、代码拆解:从示例看核心逻辑
1. 信号集的初始化与屏蔽设置
2. 未决信号的查询与打印
三、关键问题解答:穿透代码看本质
问题 1:“我可以将所有的信号都进行屏蔽,信号不就不会被处理了吗?”
问题 2:“sigset_t 是在哪里开辟的空间?”
问题 3:“未决信号的本质是什么?”
四、扩展:信号屏蔽与未决的典型应用场景
场景 1:关键逻辑的 “原子性” 保障
场景 2:批量处理未决信号
五、总结
在 Linux 系统编程中,信号是进程间通信和系统事件通知的重要机制。今天我们就以一段关于 ** 信号屏蔽(sigprocmask)和未决信号(sigpending)** 的代码为切入点,深入剖析这两个概念的底层逻辑与实际应用。
一、背景:为什么要研究信号屏蔽与未决信号?
在多任务、多事件的系统环境中,进程可能会同时收到多个信号。但有些场景下,我们希望暂时屏蔽某些信号(比如在执行关键逻辑时,不希望被干扰);同时,我们也需要知道有哪些信号被屏蔽后处于 “待处理” 状态(即未决信号)。这两个机制 ——sigprocmask
和sigpending
,就是用来解决这些问题的关键工具。
二、代码拆解:从示例看核心逻辑
先看我们的示例代码,它主要做了两件事:屏蔽所有 1-31 号信号,并循环打印当前的未决信号集合。
1. 信号集的初始化与屏蔽设置
运行
sigset_t bset, oset;
sigemptyset(&bset);
sigemptyset(&oset);
for (int i = 1; i <= 31; i++)
{sigaddset(&bset, i); // 将1-31号信号加入屏蔽集
}
sigprocmask(SIG_SETMASK, &bset, &oset);
sigset_t
:是 Linux 中用于表示信号集合的数据结构,本质上是一个位图(每一位对应一个信号)。sigemptyset
:初始化一个空的信号集合(所有位设为 0)。sigaddset
:将指定信号加入集合(对应位设为 1)。sigprocmask
:核心系统调用,用于修改进程的信号屏蔽字(即进程当前屏蔽哪些信号)。SIG_SETMASK
表示用bset
完全替换当前的屏蔽字,oset
用于保存旧的屏蔽字(以便后续恢复)。
2. 未决信号的查询与打印
运行
sigset_t pending;
while (true)
{int n = sigpending(&pending); // 获取当前进程的未决信号集合if (n < 0)continue;PrintPending(pending); // 打印未决信号的位图sleep(1);
}void PrintPending(sigset_t &pending)
{for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)) // 判断信号是否在未决集合中cout << "1";elsecout << "0";}cout << "\n\n";
}
sigpending
:获取进程的未决信号集合(即被屏蔽且已经发送到进程,但尚未被处理的信号)。sigismember
:判断某个信号是否在信号集合中。- 打印逻辑:从 31 号到 1 号信号依次判断,输出 “1” 表示该信号未决,“0” 表示未未决,这样就能直观看到未决信号的位图状态。
三、关键问题解答:穿透代码看本质
问题 1:“我可以将所有的信号都进行屏蔽,信号不就不会被处理了吗?”
回答:大部分信号会被屏蔽,但有例外!
在 Linux 中,9号信号(SIGKILL)
和19号信号(SIGSTOP)
是不可屏蔽的。这是系统级的安全设计 —— 比如SIGKILL
用于强制终止进程,若能被屏蔽,进程就可能无法被杀死,导致系统资源泄漏;SIGSTOP
用于暂停进程,同理需要保证其强制性。
所以,即使代码中尝试屏蔽所有 1-31 号信号,SIGKILL
和SIGSTOP
仍然可以突破屏蔽,作用于进程。
问题 2:“sigset_t 是在哪里开辟的空间?”
回答:在用户栈上,属于进程的用户空间。
sigset_t bset, oset;
是在main
函数中定义的局部变量,因此存储在进程的用户栈中(属于用户空间内存)。当调用sigprocmask
时,内核会读取用户空间中bset
的值,进而修改内核中进程task_struct
里的信号屏蔽字。
问题 3:“未决信号的本质是什么?”
回答:未决信号是 “被屏蔽且已送达” 的信号的临时存储。
每个进程的task_struct
中,有一个未决信号集合(pending)和一个信号屏蔽字(mask)。当一个信号被发送到进程时:
- 若该信号未被屏蔽(mask 中对应位为 0),则进程会立即处理(执行默认动作或自定义处理器);
- 若该信号被屏蔽(mask 中对应位为 1),则会被记录到未决集合(pending 中对应位置 1),直到屏蔽被解除,才会被处理。
四、扩展:信号屏蔽与未决的典型应用场景
场景 1:关键逻辑的 “原子性” 保障
在执行一段不希望被信号打断的关键逻辑时(比如数据库事务、文件写入的关键步骤),可以先屏蔽信号,执行完逻辑后再解除屏蔽:
运行
sigset_t oldmask, newmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT); // 屏蔽Ctrl+C信号(2号)sigprocmask(SIG_BLOCK, &newmask, &oldmask); // 保存旧屏蔽字,屏蔽SIGINT// 执行关键逻辑...sigprocmask(SIG_SETMASK, &oldmask, NULL); // 恢复旧屏蔽字,解除SIGINT屏蔽
场景 2:批量处理未决信号
有时我们希望 “积攒” 一批信号,然后一次性处理。可以先屏蔽信号,等业务逻辑完成后,查询未决信号并逐个处理:
运行
// 屏蔽信号
sigset_t mask, oldmask;
sigfillset(&mask); // 填充所有信号(除不可屏蔽的)
sigprocmask(SIG_SETMASK, &mask, &oldmask);// 执行业务逻辑,期间收到的信号会进入未决状态...// 查询并处理未决信号
sigset_t pending;
sigpending(&pending);
for (int signo = 1; signo <= 31; signo++)
{if (sigismember(&pending, signo)){cout << "处理未决信号:" << signo << endl;// 这里可以调用信号处理函数}
}// 恢复屏蔽字
sigprocmask(SIG_SETMASK, &oldmask, NULL);
五、总结
通过这段代码和对原理的剖析,我们可以清晰地理解:
sigprocmask
是进程 “主动控制哪些信号可以打扰自己” 的工具;sigpending
是进程 “查看有哪些被屏蔽的信号在排队等待处理” 的窗口;- 信号屏蔽与未决机制,是 Linux 系统中进程对信号 “精细管控” 的核心手段,在系统编程、服务器开发等场景中有着广泛应用。
希望这篇博客能帮大家穿透代码,真正理解信号屏蔽与未决的底层逻辑~