一、什么是信号?生活中的例子理解
想象你在图书馆看书(主程序执行中),突然:
- 手机震动(SIGINT) 有人发消息
- 闹钟响了(SIGALRM) 到午餐时间
- 管理员喊你(SIGTERM) 要闭馆了
信号就是程序执行中的“突发事件”,需要立即响应!
二、12个必会信号函数对照表
函数名 | 用途 | 日常比喻 | 使用场景 |
signal() | 设置信号处理器 | 给手机设铃声 | 快速响应信号 |
sigaction() | 高级信号处理 | 智能手机的情景模式 | 专业级信号管理 |
kill() | 发送信号 | 给别人打电话 | 进程间通信 |
raise() | 给自己发信号 | 给自己设提醒 | 程序自我管理 |
alarm() | 定时信号 | 设闹钟 | 延时/超时处理 |
pause() | 暂停等待信号 | 坐等外卖电话 | 简单信号等待 |
sigemptyset() | 清空信号集合 | 清空购物车 | 信号处理前准备 |
sigaddset() | 添加信号到集合 | 商品加入购物车 | 批量管理信号 |
sigprocmask() | 屏蔽/恢复信号 | 开勿扰模式 | 保护关键代码 |
sigpending() | 获取未处理信号 | 查看未接来电 | 诊断信号问题 |
sigsuspend() | 安全等待信号 | 带防护措施睡觉 | 高可靠性等待 |
sigqueue() | 发送带数据的信号 | 寄挂号信 | 进程间数据传输 |
三、6大核心函数详解 + 实战代码
1. 基础响应:signal()函数
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int sig_num) {
printf("收到信号: %d\n", sig_num);
}
int main() {
// 注册Ctrl+C信号处理
signal(SIGINT, signal_handler);
printf("按下Ctrl+C试试...\n");
while(1) {
sleep(1);
}
return 0;
}
效果:按下Ctrl+C打印信号数字(通常是2)而不退出程序
2. 专业处理:sigaction()函数
struct sigaction sa;
void handler(int sig) {
printf("专业处理信号 %d\n", sig);
}
int main() {
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask); // 清空屏蔽集
sa.sa_flags = SA_RESTART; // 被中断后自动重启
// 设置SIGTERM处理器
sigaction(SIGTERM, &sa, NULL);
printf("请用命令 kill %d 测试\n", getpid());
while(1) pause();
}
3. 定时功能:alarm()+ pause()组合
#include <unistd.h>
void alarm_handler(int sig) {
printf("闹钟响了!该吃午饭了!\n");
}
int main() {
signal(SIGALRM, alarm_handler);
// 设置5秒闹钟
alarm(5);
printf("设置了5秒闹钟(等待中)...\n");
pause(); // 等待闹钟信号
return 0;
}
4. 信号屏蔽:sigprocmask()保关键
sigset_t block_set;
int main() {
sigemptyset(&block_set);
sigaddset(&block_set, SIGINT); // 屏蔽Ctrl+C
printf("== 关键操作开始 ==\n");
sigprocmask(SIG_BLOCK, &block_set, NULL); // 开启屏蔽
// 这里不会被Ctrl+C中断
for(int i=0; i<3; i++) {
printf("关键操作 %d/3\n", i+1);
sleep(1);
}
sigprocmask(SIG_UNBLOCK, &block_set, NULL); // 解除屏蔽
printf("== 关键操作结束 ==\n");
return 0;
}
5. 信号通信:kill()进程交互
// receiver.c
int main() {
signal(SIGUSR1, signal_handler);
printf("接收端PID: %d\n", getpid());
pause(); // 等待信号
}
// sender.c
int main(int argc, char** argv) {
if(argc < 2) {
printf("用法: %s <接收PID>\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
kill(pid, SIGUSR1); // 发送信号
printf("已向进程%d发送信号\n", pid);
}
6. 带数据传输:sigqueue()高级通信
// 发送数据方
union sigval value;
value.sival_int = 12345; // 发送整型数据
sigqueue(receiver_pid, SIGUSR1, value);
// 接收处理函数
void handler(int sig, siginfo_t* info, void* ucontext) {
printf("收到带数据的信号!值=%d\n", info->si_value.sival_int);
}
四、实用项目示例:定时任务管理器
#include <signal.h>
#include <time.h>
#include <unistd.h>
void execute_task(int task_id) {
printf("执行任务 #%d\n", task_id);
}
void timer_handler(int sig) {
static int counter = 0;
execute_task(++counter);
alarm(3); // 每3秒重复
}
int main() {
struct sigaction sa;
sa.sa_handler = timer_handler;
sigaction(SIGALRM, &sa, NULL);
// 启动3秒定时器
alarm(3);
printf("定时任务启动...\n");
while(1) pause(); // 永久等待信号
}
五、信号处理黄金法则
处理要简单:Handler中只做标记,复杂操作回主程序
原子操作:全局变量用volatile sig_atomic_t
安全第一:volatile sig_atomic_t flag = 0; // 安全 // int flag = 0; // 危险
避开禁地:void handler() { printf("危险操作"); // 绝对禁止 malloc(100); // 地狱之门 }
六、特殊信号百科全书
信号 | 值 | 触发场景 | 能否捕获 |
SIGINT | 2 | Ctrl+C | |
SIGQUIT | 3 | Ctrl+\ | |
SIGTERM | 15 | kill默认 | |
SIGKILL | 9 | kill -9 | 强制 |
SIGSTOP | 19 | Ctrl+Z | 强制 |
SIGSEGV | 11 | 段错误 | |
SIGUSR1 | 10 | 用户自定义 | |
SIGUSR2 | 12 | 用户自定义 | |
SIGALRM | 14 | alarm() | |
SIGCHLD | 17 | 子进程终止 |
七、多进程/线程最佳实践
// 父进程等待子进程结束
void sigchld_handler(int sig) {
pid_t pid;
while((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
printf("子进程 %d 已结束\n", pid);
}
}
int main() {
// 防止子进程变僵尸
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, NULL);
// 创建子进程
if(fork() == 0) {
printf("子进程 %d 工作中...\n", getpid());
sleep(2);
exit(0);
}
// 父进程继续工作
while(1) {
printf("父进程工作中...\n");
sleep(1);
}
}