信号处理概述
在 Linux 进程信号的生命周期中,'信号捕捉'是连接'信号保存'与'信号处理'的核心环节——它允许我们自定义信号的响应逻辑,而非依赖系统默认行为(终止、忽略等)。本文将拆解信号捕捉的底层机制,结合实操代码讲清 signal() 与 sigaction() 的使用,帮助理解内核如何调用用户写的处理函数。
一、信号捕捉的核心前提:为什么信号不能立即处理
信号是异步通知机制,进程在执行用户态代码时,随时可能收到信号(如键盘 Ctrl+C 触发 SIGINT)。但内核不会立刻打断进程执行,核心原因有两个:
- 进程可能处于关键执行阶段(如操作临界资源、执行原子逻辑),立即处理会导致数据不一致
- 用户自定义的处理函数运行在用户态,需通过严格的上下文切换保证安全,避免恶意代码篡改内核资源
因此,信号捕捉的关键规则是:信号仅在'进程从内核态返回用户态'前被检查和处理——这是内核预设的'安全检查点'
二、信号捕捉的底层流程:四次特权级切换的秘密
信号捕捉的完整流程本质是'用户态↔内核态'的四次切换,可拆解为 8 个关键步骤,配合内核与用户栈的协作完成:
- 注册处理函数:进程通过
signal()或sigaction()告知内核,某信号需执行自定义函数(如handle_sigint),内核将该映射记录在task_struct->sighand->action中 - 信号产生与保存:信号通过键盘、系统调用等方式产生,内核将其标记到进程的未决信号集(pending),并检查信号屏蔽字(block)确认是否可处理
- 进入内核态:进程因系统调用(如
read)、中断(如时间片到期)或异常进入内核态,内核完成自身任务后准备返回用户态 - 内核检查信号:返回前内核扫描未决信号集,发现'未被阻塞且需自定义处理'的信号
- 构建处理上下文:内核在用户栈上构建特殊栈帧,保存原程序的寄存器状态、指令指针,同时修改返回地址为处理函数的入口地址
- 返回用户态执行处理函数:进程从内核态返回,但不再执行原程序,而是跳转到自定义处理函数(如
handle_sigint) - 处理函数返回触发 sigreturn:处理函数执行完毕后,不会直接返回原程序,而是通过隐藏的
sigreturn()系统调用再次进入内核态 - 恢复上下文并继续执行:内核通过之前保存的状态,恢复原程序的栈帧和寄存器,进程回到被信号中断的位置,继续正常执行
这一流程可概括为'用户态→内核态→用户态(处理函数)→内核态(sigreturn)→用户态(原程序)'的环形切换,sigreturn() 是确保流程闭环的关键——它负责通知内核'处理完成',并恢复原程序的执行上下文
三、信号捕捉的两种实现方式:signal () vs sigaction ()
Linux 提供两种信号捕捉接口,sigaction() 因功能更强大、行为更稳定,是生产环境的首选,signal() 仅适用于简单场景
1. 简单入门:signal () 函数
signal() 是 ANSI C 标准接口,用法简洁,适合快速绑定处理函数:
#include <stdio.h>
#include <signal.h>
#include
{
(, signum);
}
{
signal(SIGINT, handle_sigint);
();
() {
sleep();
}
;
}

