【Linux内核】Linux信号机制
Linux 信号机制的底层原理是内核通过中断式通知实现进程间 / 内核与进程间的事件传递,本质是对 “异常事件” 的异步处理机制。其核心流程涉及信号的产生、在内核中的管理、向进程的递送,以及进程对信号的处理四个阶段,底层依赖 CPU 中断、进程上下文切换和内核数据结构的协作。
信号可以理解为 “软件层面的中断”,信号则是内核或其他进程通过 “软件指令” 通知目标进程 “发生了特定事件”,迫使进程暂停当前操作,优先处理信号。例如用户按下Ctrl+C时,终端驱动会向进程发送SIGINT信号,内核会暂停进程的正常执行,转去执行SIGINT对应的处理逻辑(默认是终止进程)
信号的生命周期
信号的底层运作可拆解为四个关键阶段,每个阶段依赖内核的特定机制和数据结构
- 信号的产生(信号源触发)
信号由 “信号源” 触发,触发后并不会直接递送给进程,而是先由内核 “暂存”。常见的信号源及触发机制如下:
- 用户输入:如Ctrl+C(SIGINT),由终端驱动程序检测到按键后,通过系统调用通知内核产生对应信号。
- 进程主动发送:其他进程通过kill()、raise()等系统调用发送信号(如kill -9 ),内核会根据调用参数生成目标信号。
- 硬件异常:如进程访问无效内存(SIGSEGV)、除以零(SIGFPE),由 CPU 检测到异常后触发 “陷阱(Trap)”,内核捕获陷阱后生成对应信号。
- 内核主动触发:如定时器到期(SIGALRM)、管道破裂(SIGPIPE)、子进程退出(SIGCHLD),由内核在特定事件发生时主动生成信号。
- 信号的内核管理:“pending” 状态与进程控制块(PCB)
信号产生后,内核并不会立即 “叫醒” 进程处理,而是先将信号标记为 “待处理(pending)”,存放在进程的进程控制块(PCB,即task_struct结构体) 中。每个进程的task_struct(内核中描述进程的核心数据结构)有两个关键字段用于管理信号:
sigset_t pending
:“待处理信号集”,用位图(bitmask)表示(每个 bit 对应一个信号,如 bit15 对应SIGTERM),bit 置 1 表示信号已产生但未被处理。sigset_t blocked
:“阻塞信号集”,同样是位图,bit 置 1 表示该信号被进程暂时 “屏蔽”(即使已 pending,也不会被递送,直到阻塞解除)
这意味着,若进程阻塞了SIGINT(blocked的 bit2 置 1),此时用户按下Ctrl+C,内核会将pending的 bit2 置 1,但不会立即递送给进程,直到进程解除对SIGINT的阻塞。
- 信号的递送:进程从 “用户态” 切换到 “内核态” 时触发
信号不会 “主动打断” 进程的执行,而是在进程从用户态切换到内核态后,返回用户态前被内核 “检查并递送”。这是因为进程在用户态执行时,内核无法直接干预,只有当进程因系统调用、中断、异常等进入内核态后,内核才有机会处理信号。在进程准备从内核态返回用户态前,内核会检查该进程的pending信号集:
- 若没有未处理的信号(或信号被blocked),直接返回用户态,继续执行进程原代码。
- 若有未被阻塞的pending信号,内核会 “递送” 该信号:从pending中清除该信号(bit 置 0),然后执行信号对应的处理逻辑。
内核对信号的管理
Linux的进程控制块task_struct
中,struct sighand_struct
结构体用于存储信号处理表,而struct sigaction
是 Linux 系统中用于注册和配置信号处理方式的核心数据结构,负责设定处理信号的函数和处理信号的时候需要屏蔽的信号集合。
struct sigaction {void (*sa_handler)(int); // 处理信号的函数sigset_t sa_mask; // 处理信号时需要屏蔽的信号集int sa_flags; // 额外的行为控制标志
};
在进程的 task_struct
结构中,信号处理信息存储在:
struct task_struct {...struct signal_struct *signal; // 进程共享的信号状态,用于同一个线程组内的信号共享struct sighand_struct *sighand; // 信号处理表...
};
sighand
指向 信号处理表,其中存储了 所有信号的处理方式。
Linux信号合集
Linux中的信号分为标准信号和实时信号,标准信号是 Linux 从早期 UNIX 继承的传统信号,编号为 1~31(SIGRTMIN之前,通常SIGRTMIN=34),用于处理常见的系统事件和错误。实时信号是 POSIX 标准引入的扩展信号,编号从 SIGRTMIN(通常 34)到SIGRTMAX(通常 64),用于处理需要可靠传递的事件(如实时系统中的设备通知、进程间高优先级通信)。他们最主要的区别如下:
每个信号都有一个唯一的编号和默认行为,进程通常用SIG
前缀标识。以下是一些常见的Linux信号及其含义:
1. 常用基础信号
信号名称 | 编号 | 含义与典型场景 | 默认行为 |
---|---|---|---|
SIGTERM | 15 | 终止信号:最常用的“礼貌”终止进程的信号,允许进程清理资源后退出。 例如: kill <进程ID> 默认发送此信号。 | 终止进程 |
SIGKILL | 9 | 强制终止信号:无法被进程捕获、阻塞或忽略,直接用于 | 立即 |
SIGINT | 2 | 中断信号:用户按下 Ctrl+C 时触发,请求进程中断当前操作。 | 终止进程 |
SIGQUIT | 3 | 退出信号:用户按下 Ctrl+\ 时触发,与 SIGINT 类似,但会生成核心转储(core dump)用于调试。 | 终止进程并生成core dump |
SIGHUP | 1 | 挂起信号:通常在终端关闭时发送给关联进程,或用于通知进程重新加载配置(如nginx、systemd)。 | 终止进程(可被进程重定义为“重新加载”) |
2. 进程状态相关信号
信号名称 | 编号 | 含义与典型场景 | 默认行为 |
---|---|---|---|
SIGSTOP | 19 | 暂停信号:强制进程暂停执行(类似“冻结”),无法被忽略或捕获。 | 暂停进程 |
SIGCONT | 18 | 继续信号:恢复被 SIGSTOP 或 SIGTSTP 暂停的进程。 | 恢复进程执行 |
SIGTSTP | 20 | 终端暂停信号:用户按下 Ctrl+Z 时触发,进程可捕获并自定义处理(如进入后台)。 | 暂停进程 |
3. 错误与异常信号
信号名称 | 编号 | 含义与典型场景 | 默认行为 |
---|---|---|---|
SIGSEGV | 11 | 段错误:进程访问无效内存地址(如空指针、越界访问)时触发。 | 终止进程并生成core dump |
SIGILL | 4 | 非法指令:进程执行了无效的机器指令(如损坏的二进制文件、硬件不支持的指令)。 | 终止进程并生成core dump |
SIGFPE | 8 | 浮点异常:数学运算错误(如除以零、浮点溢出)。 | 终止进程并生成core dump |
SIGBUS | 7 | 总线错误:比 SIGSEGV 更底层的内存错误(如访问未对齐的内存地址)。 | 终止进程并生成core dump |
4. 其他常用信号
信号名称 | 编号 | 含义与典型场景 | 默认行为 |
---|---|---|---|
SIGALRM | 14 | 闹钟信号:进程通过 alarm() 或 setitimer() 设置的定时器到期时触发。 | 终止进程 |
SIGPIPE | 13 | 管道破裂:向已关闭的管道(或Socket)写入数据时触发(如客户端断开连接后服务器仍发送数据)。 | 终止进程 |
SIGCHLD | 17 | 子进程状态变化:子进程终止、暂停或继续时,内核发送给父进程,用于父进程回收子进程资源。 | 忽略(需手动处理) |