【Linux】对信号产生的内核级理解

[复制链接]
发表于 2026-10-3 13:48:16 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

信号是怎么产生的:从按键到 PCB 位图

「kill -9」「Ctrl+C 终止进程」「段错误 core dump」——这些都叫信号。信号是进程间通信的一种异步机制,但有意思的是:它并不是由某个进程「发出」的,最终都归操作系统往目标进程的 PCB 里写东西。这篇文章从硬件中断一路讲到内核数据结构,把信号产生的完整链条捋清楚。

一、键盘产生信号

1.1 先讲硬件中断

硬件中断是计算机的一种机制:外部设备需要 CPU 关注时,向 CPU 发一个中断信号。流程是——

  • 设备产生中断信号,送到中断控制器;
  • 中断控制器裁决优先级,决定先处理哪个;
  • CPU 收到后暂停当前正在跑的程序(保存程序计数器、寄存器值等现场);
  • 跳转到对应的中断服务例程执行(读硬件状态、更新数据、发应答);
  • 处理完恢复现场,继续跑原来的程序。

1.2 按一下键盘发生了什么

  1. CPU 上有物理针脚,每个针脚有编号,主板上键盘接到其中某一个。
  2. 按键时对应针脚变成高电平,CPU 触发硬件中断,保存当前进程现场。
  3. CPU 用寄存器记下是几号针脚触发的(可以理解成数组下标)。
  4. 根据这个编号在中断向量表里查到「读键盘数据」的方法;操作系统去读键盘输入。
  5. 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 / EDX32 位,做算术运算、数据操作、地址计算
变址寄存器ESI / EDI常用于数组访问和字符串操作
指针寄存器ESP / EBP堆栈指针与栈基指针,管理栈与栈上访问
指令指针EIP指向 CPU 下一条要执行的指令地址
标志寄存器EFlags存放上一条指令执行结果的状态位(进位、溢出、符号等)
控制寄存器CR2 / CR3CR2 保存页错误时的虚拟地址;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

写空指针指向的内存时:

  1. 把空指针地址(虚拟地址)和 CR3 里的页目录基地址一起交给 MMU 做地址转换。
  2. MMU 发现页表里根本没有这个虚拟地址到物理地址的映射;或者有映射但页表项权限是只读而你要写。
  3. 转换失败,出错的虚拟地址被塞进 CR2。
  4. 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企服评测-应用市场 编辑提示

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表