Linux信号机制详解
一、信号基本概念
信号是进程间通信的一种异步机制,本质上是一种软中断。
使用kill -l命令可以查看系统支持的所有信号:
kill -l
每个信号都有对应的编号和宏定义名称,这些定义可在signal.h头文件中找到。编号超过34的为实时信号,本文主要讨论编号小于34的常规信号。
二、信号的生命周期
从时间维度看,信号经历三个阶段:
- 信号产生
- 信号传递
- 信号处理
信号的处理方式包括:
- 忽略信号
- 执行默认处理动作
- 自定义信号处理函数
三、信号产生方式
1. 终端按键触发
SIGINT(Ctrl+C)和SIGQUIT(Ctrl+\)是常见的终端信号。其中SIGQUIT会导致进程终止并生成核心转储文件(Core Dump)。
核心转储是进程异常终止时将内存数据保存到磁盘的机制,主要用于调试。默认情况下系统禁用此功能,可通过以下命令启用:
ulimit -c 1024
2. 系统调用发送信号
使用kill()和raise()函数可以向进程发送信号:
#include <signal.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
while(1) {
printf("Child process running...\n");
sleep(1);
}
} else {
// 父进程发送信号
sleep(3);
kill(pid, SIGTERM);
}
return 0;
}
abort()函数用于使进程异常终止:
#include <stdlib.h>
void test_abort() {
printf("About to abort...\n");
abort();
}
3. 软件条件触发
alarm()函数用于设置定时器,超时后产生SIGALRM信号:
#include <unistd.h>
#include <signal.h>
void alarm_handler(int sig) {
printf("Alarm triggered!\n");
}
int main() {
signal(SIGALRM, alarm_handler);
alarm(5); // 5秒后触发
pause(); // 等待信号
return 0;
}
4. 硬件异常触发
CPU检测到硬件异常时会生成相应信号:
- 除零操作 → SIGFPE
- 非法内存访问 → SIGSEGV
- 无效指令 → SIGILL
四、信号处理机制
基础信号捕获
#include <stdio.h>
#include <signal.h>
void signal_handler(int sig_num) {
printf("Received signal: %d\n", sig_num);
}
int main() {
signal(SIGINT, signal_handler);
printf("Press Ctrl+C to test signal handling\n");
while(1) {
sleep(1);
}
return 0;
}
模拟内存访问异常
#include <signal.h>
void segv_handler(int sig) {
printf("Segmentation fault caught: signal %d\n", sig);
exit(1);
}
int main() {
signal(SIGSEGV, segv_handler);
int *null_ptr = NULL;
*null_ptr = 42; // 触发段错误
return 0;
}
五、信号状态管理
核心概念
- 信号递达:实际执行信号处理动作
- 信号未决:信号已产生但尚未递达的状态
- 信号阻塞:进程暂时阻止某些信号的递达
内核中的信号表示
内核为每个进程维护以下信息:
- 未决信号集:记录已产生但未处理的信号
- 阻塞信号集:记录被阻塞的信号
- 信号处理函数指针数组
信号集操作
使用sigset_t类型管理信号集:
#include <signal.h>
int main() {
sigset_t set, old_set;
// 初始化信号集
sigemptyset(&set);
// 添加信号到集合
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
// 设置进程信号屏蔽字
sigprocmask(SIG_BLOCK, &set, &old_set);
// 执行关键代码段
// 恢复原信号屏蔽字
sigprocmask(SIG_SETMASK, &old_set, NULL);
return 0;
}
常用信号集操作函数:
sigemptyset():清空信号集sigfillset():填充所有信号sigaddset():添加指定信号sigdelset():删除指定信号sigismember():检查信号是否存在
信号屏蔽操作
// 获取当前未决信号集
sigset_t pending_set;
sigpending(&pending_set);
// 检查特定信号状态
if (sigismember(&pending_set, SIGINT)) {
printf("SIGINT is pending\n");
}
六、高级信号处理
sigaction结构
#include <signal.h>
void advanced_handler(int sig) {
printf("Advanced signal handler for %d\n", sig);
}
int main() {
struct sigaction act, old_act;
act.sa_handler = advanced_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, &old_act);
while(1) {
pause();
}
return 0;
}
可重入性问题
信号处理函数应避免调用不可重入函数:
- 内存分配函数(malloc/free)
- 标准I/O函数
- 任何使用全局状态的函数
volatile关键字
用于防止编译器优化信号相关的全局变量:
volatile int signal_flag = 0;
void sig_handler(int sig) {
signal_flag = 1;
}
int main() {
signal(SIGINT, sig_handler);
while (!signal_flag) {
// 等待信号
}
printf("Signal received!\n");
return 0;
}