
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- 前言
- [1 ~> 信号的"生活化"初识](#1 ~> 信号的“生活化”初识)
- [2 ~> 信号的产生方式:它是怎么来的?](#2 ~> 信号的产生方式:它是怎么来的?)
-
- [2.1 终端按键产生](#2.1 终端按键产生)
- [2.2 系统调用产生](#2.2 系统调用产生)
- [2.3 软件条件产生](#2.3 软件条件产生)
- [2.4 硬件异常产生](#2.4 硬件异常产生)
- [3 ~> 核心原理:信号是如何被"记住"的?](#3 ~> 核心原理:信号是如何被“记住”的?)
- [4 ~> 信号处理的"黄金时机":内核态切换](#4 ~> 信号处理的“黄金时机”:内核态切换)
-
- [4.1 两种状态的区别](#4.1 两种状态的区别)
- [4.2 处理流程("倒 8 字"模型)](#4.2 处理流程(“倒 8 字”模型))
- [5 ~> 实战演练:捕捉与屏蔽](#5 ~> 实战演练:捕捉与屏蔽)
-
- [5.1 实验:让 Ctrl + C 失效](#5.1 实验:让 Ctrl + C 失效)
- [5.2 信号集操作:封印信号](#5.2 信号集操作:封印信号)
- [6 ~> 编写安全代码的"三大准则"](#6 ~> 编写安全代码的“三大准则”)
-
- [6.1 可重入函数](#6.1 可重入函数)
- [6.2 volatile 关键字](#6.2 volatile 关键字)
- [6.3 优雅回收子进程:SIGCHLD](#6.3 优雅回收子进程:SIGCHLD)
- [7 ~> 遇到的问题与解决思路](#7 ~> 遇到的问题与解决思路)
- [8 ~> 总结与延伸](#8 ~> 总结与延伸)
- 结尾
前言
在 Linux 的多任务世界里,进程并不是孤立运行的。它们需要一种机制来应对突发事件------比如用户按下了 Ctrl + C,或者程序不小心除以了 0。这种"异步通信"的机制就是信号(Signal)。
最近通过对 Linux 进程信号的学习,我发现它不仅是系统编程的基石,更像是一套完整的"紧急应变系统"。今天这篇博客,我将带大家从信号的产生、保存、处理到内核底层原理进行全方位的深度拆解。
1 ~> 信号的"生活化"初识
为了直观理解信号,我们可以对比一个生活场景:
你在网上买了快递,在快递寄达前,你其实已经知道该怎么处理它了(拆开、送人或扔掉)。快递员给你打电话(信号到来)是异步的,你不知道他什么时候会打。电话响时,你可能正在忙(比如打游戏),无法立即去取,但你会"记住"这件事,等手头忙完(合适的时间)再去处理。
核心结论:
-
识别信号:进程识别信号是内核预设的特性。
-
处理预设:信号还没产生,进程就已经知道对应的处理方法了。
-
异步性:信号可能在进程执行的任何时刻到来。
2 ~> 信号的产生方式:它是怎么来的?
信号不会凭空产生,通常有以下四种渠道:
2.1 终端按键产生
这是我们最常用的方式:
Ctrl + C:对应 2) SIGINT(中断信号),默认动作是终止前台进程。
Ctrl + \:对应 3) SIGQUIT (退出信号),默认动作是终止并产生 Core Dump 文件(核心转储,用于事后调试)。
Ctrl + Z:对应 20) SIGTSTP(停止信号),将进程挂起。
2.2 系统调用产生
-
kill命令 / 函数:可以向指定 PID 发送任意信号。 -
raise函数:进程"自残",给自己发信号。 -
abort函数:发送 6 ) SIGABRT 使进程异常终止。
2.3 软件条件产生
-
闹钟机制 :使用
alarm函数。当时间到达,内核向进程发送 14 ) SIGALRM。 -
管道破裂 :读端关闭的管道继续写入时,产生 13 ) SIGPIPE。
2.4 硬件异常产生
-
除 0 错误 :CPU 运算单元出错,内核发送 8) SIGFPE。
-
野指针 / 越界 :MMU 内存管理单元出错,内核发送 11) SIGSEGV。
3 ~> 核心原理:信号是如何被"记住"的?
信号的产生是异步的,进程可能正在处理更重要的事情。那么信号在被递达前存在哪呢?
在内核的 task_struct(进程控制块) 中,维护了三张至关重要的表:
-
Pending 位图:记录收到了哪些信号。每一位代表一个信号,1 表示信号已到达但未处理。
-
Block 位图:记录哪些信号被阻塞。如果某位为 1,说明即使收到该信号,进程也会暂时"封印"它,不予递达。
-
Handler 表:这是一个函数指针数组,记录了每个信号对应的处理动作。
信号的状态术语:
-
递达 (Delivery):实际执行信号处理动作。
-
未决 (Pending):从信号产生到递达之间的状态。
-
阻塞 (Block):进程可以选择阻塞某个信号。一旦被阻塞,信号将保持在未决状态,直到解除阻塞。
4 ~> 信号处理的"黄金时机":内核态切换
这是最硬核的部分:信号到底是什么时候被处理的?
结论:当进程从内核态(Kernel Mode)返回到用户态(User Mode)的时候。
4.1 两种状态的区别
-
用户态:执行用户代码,权限受限。
-
内核态:执行 OS 代码,拥有最高权限。通过系统调用、硬件中断或异常切换。
4.2 处理流程("倒 8 字"模型)
-
陷入内核:因为系统调用(如 read)或时间片到了,进入内核态。
-
信号检测:在准备返回用户态前,检查 pending 位图。
-
处理信号:如果是自定义捕捉,OS 会切换回用户态执行 handler。执行完后,通过特殊的系统调用再次进入内核。
-
返回原点:最后才返回用户态的主控制流继续执行。
5 ~> 实战演练:捕捉与屏蔽
5.1 实验:让 Ctrl + C 失效
我们可以通过 signal() 或更强大的 sigaction() 来拦截信号。
cpp
#include <iostream>
#include <signal.h>
#include <unistd.h>
void handler(int signum) {
std::cout << "捕捉到信号: " << signum << ",嘿嘿,杀不死我!" << std::endl;
}
int main() {
// 注册信号捕捉
signal(SIGINT, handler);
while(true) {
std::cout << "进程运行中 [PID:" << getpid() << "]..." << std::endl;
sleep(1);
}
return 0;
}
5.2 信号集操作:封印信号
如果我想在一段敏感代码执行期间屏蔽所有信号,可以使用信号集操作:
cpp
sigset_t set, oset;
sigemptyset(&set);
sigaddset(&set, SIGINT); // 准备屏蔽 2 号信号
// 1. 设置阻塞位图
sigprocmask(SIG_BLOCK, &set, &oset);
// 2. 执行关键任务...
// 3. 恢复原始屏蔽状态
sigprocmask(SIG_SETMASK, &oset, NULL);
6 ~> 编写安全代码的"三大准则"
在信号处理中,有些坑如果不踩一次真的很难记住:
- 可重入函数
- volatile
- SIGCHLD信号
6.1 可重入函数
问题:如果你的主控制流正在 insert 链表,信号突然来了,handler 里也调用了 insert,链表结构就会被破坏。
准则:在信号处理函数里,避免调用不可重入函数(如 malloc,printf,标准 IO 库等)。
6.2 volatile 关键字
在优化级别较高时,编译器可能为了效率把一个全局变量放在寄存器里。
现象:handler 改了内存里的变量,但主循环还在看寄存器旧值,导致死循环。
对策:使用 volatile 修饰变量,强制要求编译器每次从内存读取。
6.3 优雅回收子进程:SIGCHLD
以前我们需要循环 waitpid 来回收子进程。
黑科技:子进程退出时会发 SIGCHLD 给父进程。如果我们不关心退出状态,直接执行 signal(SIGCHLD, SIG_IGN);。在 Linux 下,这能让子进程退出后自动释放,彻底告别僵尸进程!
7 ~> 遇到的问题与解决思路
Q:为什么捕获了"除 0 异常"后,程序会陷入死循环打印?
排查: 除 0 异常由 CPU 寄存器状态触发。当从内核态返回执行 handler 后,CPU 里的异常标志位并没有被清除。返回后 CPU 再次检测到异常,再次发信号。
解决: 硬件异常信号通常不建议只做简单的捕获,应该在 handler 中直接 exit() 或由系统默认动作终止。
8 ~> 总结与延伸
通过本次学习,我深刻体会到信号是 Linux 异步处理机制的灵魂:
-
产生:来源多样(按键、软件、硬件)。
-
保存:通过
task_struct中的位图管理。 -
处理:利用内核态切换的时机进行递达。
-
安全:注意可重入性与
volatile可见性。
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
