在Linux系统中,信号(Signal)是进程间通信的关键机制,通常用于通知进程发生特定事件。例如,用户按下Ctrl+C
会发送SIGINT
信号来终止进程,或程序访问非法内存时触发SIGSEGV
信号。信号处理是进程响应这些事件的核心机制。
一、信号分类 ⚡
Linux支持多种信号(可通过kill -l
查看)。常见信号及其默认行为如下:
-
终止进程:
-
SIGTERM
(优雅终止) -
SIGKILL
(强制终止,不可捕获) -
SIGINT
(Ctrl+C)
-
-
核心转储:
-
SIGSEGV
(段错误) -
SIGFPE
(算术错误)
-
-
挂起/继续:
-
SIGHUP
(终端断开) -
SIGSTOP
(暂停进程,不可捕获)
-
-
其他:
-
SIGALRM
(定时器) -
SIGCHLD
(子进程状态变化)
-
二、信号处理函数 📜
进程可通过注册信号处理函数自定义信号行为,常用的API如下:
-
signal()
函数 🛠️#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
-
功能 :为信号
signum
绑定处理函数handler
。 -
示例:
void handler(int sig) { printf("Received SIGINT\n"); exit(0); } signal(SIGINT, handler); // 捕获Ctrl+C
-
缺点:行为因系统而异,缺乏灵活性(如无法控制信号屏蔽)。
-
sigaction()
函数(推荐) 👍#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
-
参数:
-
act
:指定新行为,通过struct sigaction
配置:-
sa_handler
:处理函数(或SIG_IGN
/SIG_DFL
)。 -
sa_mask
:在处理信号时阻塞其他信号。 -
sa_flags
:控制行为(如SA_RESTART
自动重启被中断的系统调用)。
-
-
oldact
:保存旧的处理配置。
-
-
示例:
struct sigaction act; act.sa_handler = handler; sigemptyset(&act.sa_mask); // 不阻塞其他信号 act.sa_flags = 0; sigaction(SIGINT, &act, NULL);
三、编写信号处理函数的注意事项 ⚠️
-
可重入性 :避免使用非异步安全函数(如
printf
、malloc
),可能引发竞态条件。- 使用volatile变量 或原子操作传递状态到主程序。
-
执行时间短:避免长时间操作,尽快返回。
-
避免修改全局状态:如需修改,需通过信号屏蔽等手段保证原子性。
四、信号阻塞与解除 🛑
通过信号掩码控制进程对信号的响应:
-
sigprocmask()
:设置/修改进程的信号掩码。 -
sigpending()
:检查未决(被阻塞)的信号。sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞SIGINT
五、实时信号(RT Signals) ⏱️
-
范围 :
SIGRTMIN
到SIGRTMAX
(如32-64)。 -
特点 :支持排队(避免丢失)、可携带额外数据(通过
sigqueue()
发送)。 -
使用:
union sigval value; value.sival_int = 123; sigqueue(pid, SIGRTMIN, value); // 发送附带数据的信号
六、发送信号的常用方法 📬
-
命令 :
kill -信号 PID
(如kill -9 1234
)。 -
系统调用:
-
kill()
:向指定进程发送信号。 -
raise()
:向自身发送信号。 -
alarm()
:设置定时器(触发SIGALRM
)。
-
七、多线程中的信号处理 🔄
-
信号处理函数是进程级:所有线程共享同一处理函数。
-
信号掩码是线程级:各线程可独立设置阻塞的信号。
-
建议:
-
主线程统一处理信号(通过
sigwait()
同步等待)。 -
避免在多线程中使用异步信号处理。
-
八、典型应用场景 🏗️
-
优雅退出 :捕获
SIGTERM
释放资源。 -
定时任务 :通过
SIGALRM
触发定时操作。 -
错误调试 :处理
SIGSEGV
记录错误信息。
示例:使用sigaction
处理SIGINT
📝
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
volatile sig_atomic_t flag = 0;
void handler(int sig) {
flag = 1; // 安全设置标志位
}
int main() {
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
while (!flag) {
sleep(1);
printf("Running...\n");
}
printf("Exiting gracefully\n");
return 0;
}
总结 🏁
Linux信号处理是控制进程行为的重要机制。通过合理使用signal()
或sigaction()
注册处理函数,结合信号阻塞、实时信号等特性,可以实现灵活的事件响应。需要特别注意处理函数的可重入性,避免竞态条件,确保程序健壮性。