马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
信号是怎么产生的:从按键到 PCB 位图
「kill -9」「Ctrl+C 终止进程」「段错误 core dump」——这些都叫信号。信号是进程间通信的一种异步机制,但有意思的是:它并不是由某个进程「发出」的,最终都归操作系统往目标进程的 PCB 里写东西。这篇文章从硬件中断一路讲到内核数据结构,把信号产生的完整链条捋清楚。
一、键盘产生信号
1.1 先讲硬件中断
硬件中断是计算机的一种机制:外部设备需要 CPU 关注时,向 CPU 发一个中断信号。流程是——
- 设备产生中断信号,送到中断控制器;
- 中断控制器裁决优先级,决定先处理哪个;
- CPU 收到后暂停当前正在跑的程序(保存程序计数器、寄存器值等现场);
- 跳转到对应的中断服务例程执行(读硬件状态、更新数据、发应答);
- 处理完恢复现场,继续跑原来的程序。
1.2 按一下键盘发生了什么
- CPU 上有物理针脚,每个针脚有编号,主板上键盘接到其中某一个。
- 按键时对应针脚变成高电平,CPU 触发硬件中断,保存当前进程现场。
- CPU 用寄存器记下是几号针脚触发的(可以理解成数组下标)。
- 根据这个编号在中断向量表里查到「读键盘数据」的方法;操作系统去读键盘输入。
- OS 判断输入的是命令还是普通数据:普通数据直接写入键盘文件缓冲区,让相应进程来读;如果是命令,就把它解释成信号,发送给对应进程。
所以 Ctrl + C 的本质不是「按了 C 键」,而是终端驱动把 Ctrl+C 这个字符序列解释成 SIGINT,再写给前台进程组。
1.3 信号在内核里长什么样
信号发给进程后,进程 PCB 里其实用一个 32 位整数保存收到的信号——也就是位图。Linux 里异步信号共 31 个(1~31),32 位刚好装得下。
发送信号的本质 = 把位图里对应的比特位从 0 置 1,就算「发出去了」。因为要改 PCB 这个内核数据结构,所以不管通过什么方式产生信号(键盘、kill、异常、内核态),最终都得由操作系统把信号写进目标进程的 PCB。这也解释了为什么信号是「内核级」的。
二、异常产生信号
2.1 先认识几个 CPU 寄存器
异常类信号和寄存器强相关,先把名字认全:
| 类别 | 寄存器 | 作用 |
| 通用寄存器 | EAX / EBX / ECX / EDX | 32 位,做算术运算、数据操作、地址计算 |
| 变址寄存器 | ESI / EDI | 常用于数组访问和字符串操作 |
| 指针寄存器 | ESP / EBP | 堆栈指针与栈基指针,管理栈与栈上访问 |
| 指令指针 | EIP | 指向 CPU 下一条要执行的指令地址 |
| 标志寄存器 | EFlags | 存放上一条指令执行结果的状态位(进位、溢出、符号等) |
| 控制寄存器 | CR2 / CR3 | CR2 保存页错误时的虚拟地址;CR3 指向最高级页目录的物理地址 |
2.2 除零 → SIGFPE
程序里出现除零时,CPU 做除法会让 EFlags 的溢出标志位被置 1。CPU 识别到这个标志位变化,就转去通知操作系统,操作系统随即向当前进程发送 SIGFPE(8 号信号)终止进程。
int main() { int a = 1, b = 0; return a / b; } // 运行后 Bash 提示 Floating point exception
2.3 野指针 → SIGSEGV
写空指针指向的内存时:
- 把空指针地址(虚拟地址)和 CR3 里的页目录基地址一起交给 MMU 做地址转换。
- MMU 发现页表里根本没有这个虚拟地址到物理地址的映射;或者有映射但页表项权限是只读而你要写。
- 转换失败,出错的虚拟地址被塞进 CR2。
- CPU 通知操作系统「CR2 里有页错误」,OS 直接给对应进程发 SIGSEGV(11 号信号),进程终止。
这就是 Segmentation fault (core dumped) 的来源,core 文件里存的就是崩溃瞬间的内存镜像,用 gdb 打开能直接看到崩在哪一行。
三、总结
一句话收尾:异常产生信号,一定是硬件和软件共同作用的结果。硬件(CPU 标志位、MMU 页错误)先发现异常,通过中断/异常入口通知内核,操作系统再把对应的信号比特位置进进程的 PCB 位图里。
反过来推回去也很实用:
- 看到
Segmentation fault → 查空指针/越界访问,看 /proc/PID/core 或 ulimit 开的 core 文件。
- 看到
Floating point exception → 除零,检查分母来源。
kill -9 无法被捕获 → 因为 9 号信号(SIGKILL)是直接写位图后内核强制收尾的,不允许注册 handler,也不管进程当前阻塞在哪。
- 想优雅退出 → 用 SIGTERM(15),进程可以 catch 住做清理;这就是为什么脚本里「停止服务」应该先
kill <pid> 而非直接 -9。
小结
信号的产生只有三种入口:终端/命令(键盘+kill)、异常(CPU _flags / MMU 触发)、内核态主动发送,但落到进程身上只有一种形式——PCB 里的位图被置位。搞清楚这条链,再看信号相关的 API(signal、sigaction、kill、sigprocmask)就只是操作这同一套数据结构的上层封装。
qidao123.com企服评测-应用市场 编辑提示 |