Linux信号保存与处理机制详解
Linux信号的保存与处理涉及多个关键机制,以下是详细的总结:
1. 信号的保存
- 进程描述符(task_struct):每个进程的PCB中包含信号相关信息。
- pending信号集:记录已到达但未处理的信号(未决信号)。每个信号对应一个位,置1表示信号待处理。
- blocked信号屏蔽字:标识被阻塞(屏蔽)的信号。被屏蔽的信号将暂不递送,直到解除屏蔽。
- 实时信号队列:实时信号(SIGRTMIN~SIGRTMAX)支持排队,内核为每个进程维护多个队列,确保多次发送的同一信号被依次处理。
2. 信号的处理方式
- 默认动作:如终止进程(SIGTERM)、忽略(SIGCHLD)或生成核心转储(SIGSEGV)。
- 忽略信号:通过
SIG_IGN
显式忽略,但SIGKILL和SIGSTOP不可忽略。 - 自定义处理函数:用户注册的信号处理函数(通过
signal()
或sigaction()
),需注意函数需为异步信号安全。
3. 信号处理流程
- 信号产生:通过硬件(如除零)、终端(Ctrl+C)、
kill()
系统调用等方式触发。 - 记录到pending:内核将信号标记在目标进程的pending集合中。
- 递送检查:在进程从内核态返回用户态时(如系统调用结束、中断处理完成),检查未被屏蔽的pending信号。
- 执行处理函数:
- 内核临时切换到用户态执行处理函数。
- 处理函数返回后,通过
sigreturn
系统调用恢复原上下文。 - 默认情况下,正在处理的信号会被自动阻塞,防止重入。
4. 关键系统调用与函数
- sigprocmask:修改进程的信号屏蔽字,控制哪些信号被阻塞。
- sigaction:设置信号处理方式,支持设置SA_RESTART(自动重启被中断的系统调用)及SA_SIGINFO(携带额外信息)。
- sigpending:获取当前未决的信号集。
- kill/raise:向进程或自身发送信号。
- alarm:设置定时器发送SIGALRM。
5. 实时信号 vs 非实时信号
- 非实时信号(标准信号):如SIGINT、SIGTERM,不支持排队,多次发送可能丢失。
- 实时信号:通过
sigqueue()
发送并附带数据,支持可靠排队,确保多次信号被依次处理。
6. 多线程中的信号处理
- 线程拥有独立的信号屏蔽字,但信号处理函数为进程内共享。
- 信号可定向到特定线程(如通过
pthread_kill()
),未处理的信号由主线程处理。
7. 注意事项
- 可重入性:处理函数应使用异步信号安全函数(如
write()
),避免调用printf()
、malloc()
等非安全函数。 - 原子性:通过
sig_atomic_t
类型变量在信号处理函数与主程序间安全传递状态。 - 竞态条件:正确处理信号屏蔽与临界区保护,防止条件竞争。
示例场景
#include <signal.h>
#include <stdio.h>
#include <unistd.h>void handler(int sig) {write(STDOUT_FILENO, "Received SIGINT!\n", 17);
}int main() {struct sigaction sa;sa.sa_handler = handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART;sigaction(SIGINT, &sa, NULL);while(1) {pause(); // 等待信号}return 0;
}
此代码注册SIGINT的处理函数,按下Ctrl+C时打印消息,处理期间自动阻塞SIGINT,避免递归调用。
总结
理解Linux信号的保存与处理机制,需掌握信号的生命周期(产生、保存、递送)、处理函数的设计约束(可重入性、安全性),以及多线程环境下的复杂性。合理使用信号能提升程序的健壮性,但需谨慎处理异步事件带来的挑战。