信号是 Unix/Linux 系统中进程间通信的一种异步机制。本文将详细介绍 C 语言中的信号处理机制、常见函数与最佳实践。
什么是信号?
信号是一种软中断,用于通知进程发生了某个事件。常见信号包括:
- SIGINT (Ctrl+C) - 终端中断信号
- SIGTERM - 终止信号(优雅退出)
- SIGKILL - 强制终止信号(不可捕获)
- SIGSEGV - 段错误信号
- SIGALRM - 定时器信号
signal() 函数
最简单的信号处理方式是用 signal() 函数注册处理函数:
signal_basic.c
#include <stdio.h> #include <signal.h> #include <unistd.h> void handle_sigint(int sig) { printf("\n收到 SIGINT 信号,正在安全退出...\n"); /* 注意:这里不能调用不安全的函数 */ } int main(void) { /* 注册 SIGINT 信号处理函数 */ signal(SIGINT, handle_sigint); printf("按 Ctrl+C 退出程序\n"); while (1) { sleep(1); } return 0; }
sigaction() 函数
推荐使用 sigaction(),它比 signal() 更可靠且可移植性更好:
sigaction_demo.c
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <setjmp.h> static sigjmp_buf jump_buffer; volatile sig_atomic_t jump_ok = 0; void sigalarm_handler(int sig) { if (jump_ok) siglongjmp(jump_buffer, 1); } int main(void) { struct sigaction sa; sa.sa_handler = sigalarm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); if (sigsetjmp(jump_buffer, 1) == 0) { jump_ok = 1; alarm(3); printf("等待 3 秒超时...\n"); sleep(10); } else { printf("超时!跳回执行\n"); } return 0; }
信号处理的安全准则
在信号处理函数中必须注意以下几点:
- 使用异步信号安全函数 - 只能调用 async-signal-safe 的函数,如 write()、_exit(),不能调用 printf()、malloc() 等
- 设置全局标志 - 在处理函数中只设置标志位,在主程序中处理实际逻辑
- 使用 sig_atomic_t - 对于布尔标志,使用 sig_atomic_t 类型确保原子操作
- 注意可重入性 - 避免在信号处理函数中访问可能被中断的代码使用的数据
常见应用场景
1. 超时处理
使用 alarm() 和 sigaction() 实现操作超时:
timeout_demo.c
/* 简化版超时处理 */ void timeout_handler(int sig) { /* 设置全局超时标志 */ timeout_flag = 1; } /* 在需要超时的操作前设置定时器 */ alarm(5); /* 5秒后触发 SIGALRM */
2. 优雅退出
处理 SIGTERM 实现优雅退出:
graceful_exit.c
volatile sig_atomic_t running = 1; void sigterm_handler(int sig) { running = 0; /* 收到信号后退出主循环 */ } /* 主循环中检查标志 */ while (running) { /* 处理业务 */ } /* 清理资源并退出 */ cleanup();
总结
信号处理是 C 语言系统编程中的重要内容。使用 sigaction() 而非 signal(),遵循异步信号安全准则,可以写出健壮可靠的多线程/多进程程序。