题目
设计安全的SIGINT信号处理函数
信息
- 类型:问答
- 难度:⭐⭐
考点
信号处理,异步安全,竞态条件,可重入函数
快速回答
设计安全的SIGINT处理函数需注意:
- 使用
volatile sig_atomic_t声明全局标志 - 仅调用异步安全函数(如
write、gettimeofday) - 避免复杂逻辑和不可重入函数
- 使用原子操作处理共享状态
- 考虑信号丢失和多次触发的处理
原理说明
信号处理函数在执行时会中断程序正常流程,因此必须满足:
- 异步安全性:只能调用POSIX定义的异步安全函数(如
write、gettimeofday) - 原子操作:全局变量需使用
sig_atomic_t保证读写原子性 - 避免竞态:信号可能在任何时刻触发,需防止与主程序的状态冲突
- 可重入性:避免使用静态缓冲区或全局状态
代码示例
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
volatile sig_atomic_t should_exit = 0;
struct timeval interrupt_time;
void handle_sigint(int sig) {
// 异步安全地记录时间
gettimeofday(&interrupt_time, NULL);
// 原子设置退出标志
should_exit = 1;
// 简单日志(异步安全)
const char msg[] = "SIGINT received\n";
write(STDERR_FILENO, msg, sizeof(msg)-1);
}
int main() {
struct sigaction sa;
sa.sa_handler = handle_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
while(!should_exit) {
// 主循环工作
// 注意:格式化输出等非安全操作应放在主循环
// 例如:printf("Last interrupt at: %ld.%06ld\n",
// interrupt_time.tv_sec, interrupt_time.tv_usec);
}
return 0;
}最佳实践
- 保持简洁:信号处理函数只做最小操作(设置标志/记录时间)
- 屏蔽信号:使用
sa_mask屏蔽其他信号防止嵌套中断 - 自管道技巧:通过管道通知主线程,避免轮询全局变量
- 原子标志:
sig_atomic_t确保标志读写不被中断
常见错误
- 使用非安全函数:在处理函数中调用
printf/malloc等可能导致死锁 - 忽略可重入性:使用静态缓冲区(如
strtok)引发数据损坏 - 竞态条件:未保护对复杂数据结构的访问
- 信号丢失:连续快速触发时丢失信号(应结合
sigprocmask)
扩展知识
- 异步安全函数列表:参考POSIX标准的Signal Safety章节
- 实时信号:
SIGRTMIN以上信号支持队列化,避免丢失 - 信号处理线程模型:Linux中信号可能由任意线程处理,需注意线程安全
- 替代方案:
signalfd(Linux特有)可将信号转为文件描述符事件