C语言 <signal.h> 标准库详解
信号处理机制通俗解读与实用指南
什么是信号?
信号(Signal)是操作系统内核用来通知进程发生了某种事件的机制。可以把信号想象成手机的来电通知:
🎯 场景1:当你按下 Ctrl+C 时,终端程序会收到一个 SIGINT
信号(就像手机接到电话)
🎯 场景2:程序访问无效内存地址时,系统会发送 SIGSEGV
信号(就像手机收到电池过热的警告)
🎯 场景3:当一个子进程结束时,内核会向父进程发送 SIGCHLD
信号(就像手机收到短信通知)
信号是软件中断的一种形式,允许进程中断当前执行流程来处理特定事件。
<signal.h>的功能
这个头文件提供了信号处理所需的函数、类型和常量,主要包括:
- 定义信号类型:如 SIGINT、SIGSEGV 等
- 注册信号处理函数:告诉系统当特定信号到来时应该执行什么操作
- 发送信号:允许进程向自己或其他进程发送信号
- 阻塞信号:暂时阻止某些信号被接收
信号处理的哲学
信号处理就像消防演习 – 平时定义好应对突发事件的方案(信号处理函数),当警报响起(信号到来)时,程序会暂停当前工作,执行预定方案,完成后继续原来的任务。
常用信号类型
信号名称 | 默认操作 | 触发场景 | 数值 |
---|---|---|---|
SIGINT | 终止进程 | 用户按下中断键(Ctrl+C) | 2 |
SIGQUIT | 终止+核心转储 | 用户按下退出键(Ctrl+\) | 3 |
SIGKILL | 终止进程 | 强制杀死进程(无法捕获或忽略) | 9 |
SIGSEGV | 终止+核心转储 | 无效内存访问(段错误) | 11 |
SIGTERM | 终止进程 | 优雅终止进程(系统关机时常用) | 15 |
SIGALRM | 终止进程 | 定时器超时(闹钟信号) | 14 |
SIGCHLD | 忽略 | 子进程状态改变 | 17 |
注册信号处理函数
使用 signal()
函数告诉系统:”当某个信号出现时,请执行我指定的函数”。
函数原型
void (*signal(int sig, void (*func)(int)))(int);
这个看起来复杂的声明可以理解为:
signal(信号编号, 处理函数) -> 返回原来的处理函数
参数说明
sig
:要处理的信号(如 SIGINT)func
:信号处理函数指针,也可以是特殊值:SIG_IGN
:忽略该信号SIG_DFL
:恢复默认处理
示例:捕获SIGINT信号
#include <stdio.h>
#include <signal.h>
// 信号处理函数
void sigint_handler(int sig) {
printf("\n接收到SIGINT信号(%d)! 但我不退出,按Ctrl+\\退出程序\n", sig);
}
int main() {
// 注册SIGINT信号处理函数
signal(SIGINT, sigint_handler);
printf("按Ctrl+C试试...\n");
while(1) {
// 无限循环,等待信号
}
return 0;
}
运行效果:按Ctrl+C不会终止程序,而是执行自定义操作
发送信号
可以使用 raise()
函数向当前进程发送信号:
函数原型
int raise(int sig);
示例:给自己发送信号
#include <stdio.h>
#include <signal.h>
void alarm_handler(int sig) {
printf("叮铃铃! 闹钟响了 (SIGALRM)\n");
}
int main() {
signal(SIGALRM, alarm_handler);
printf("设置3秒后闹钟...\n");
// 3秒后发送SIGALRM信号
alarm(3);
printf("等待闹钟响起...\n");
pause(); // 暂停直到收到信号
return 0;
}
输出:3秒后打印”叮铃铃! 闹钟响了 (SIGALRM)”
注意:
kill()
函数可以向其他进程发送信号,但需要知道目标进程ID(PID),使用时需谨慎。
信号处理的重要注意事项
1. 异步执行风险
信号处理函数可能在程序运行的任何时刻被调用(异步执行),因此在处理函数中应避免调用不可重入函数(如printf、malloc等)。
2. 信号处理函数的限制
信号处理函数应该尽量简单,只设置标志位或执行简单操作。复杂操作应在主循环中处理。
3. 无法捕获的信号
SIGKILL(9) 和 SIGSTOP(19) 信号无法被捕获、阻塞或忽略,它们总是立即终止或暂停进程。
4. 信号处理函数重置
某些系统在信号处理函数执行后,会将该信号的处理方式重置为默认行为。为避免这种情况,可以在处理函数中重新注册。
5. 全局变量与volatile
在信号处理函数和主程序之间共享的变量应声明为volatile
,防止编译器优化导致意外结果。
关键知识点
- 信号是软件中断机制,用于通知进程事件发生
- 每个信号都有默认处理方式(终止、忽略、核心转储等)
- 使用signal()函数自定义信号处理
- SIG_IGN忽略信号,SIG_DFL恢复默认处理
- raise()函数向当前进程发信号
- 信号处理函数应尽可能简单
- 异步执行特性带来编程复杂性
- 某些关键信号不可捕获(SIGKILL/SIGSTOP)
实际应用场景
📌 服务器程序:捕获SIGTERM信号实现优雅关闭
📌 长时间任务:使用SIGALRM设置超时
📌 多进程程序:处理SIGCHLD信号回收子进程
📌 终端程序:自定义Ctrl+C行为
信号处理最佳实践
✅ 保持处理函数简短
✅ 仅使用异步安全函数
✅ 使用volatile sig_atomic_t类型标志变量
✅ 处理完成后重新注册处理函数
⚠️ 避免在信号处理函数中调用复杂操作
© 2023 C语言信号处理详解 | 编程小白友好的signal.h指南