在 Linux 中,信号(signal)是用于进程间通信的一种机制。信号可以通知一个进程发生了某种事件或者需要进行某些操作。信号通常是由操作系统内核发送给进程,也可以由其他进程发送给目标进程。
1. 信号的基本概念
- 信号类型:信号是一种异步通知机制,通常由内核或其他进程触发。
- 信号传递:信号会传递给目标进程,目标进程在接收到信号后会执行默认的处理行为,或者根据程序的设定执行自定义处理。
- 信号的目标:信号可以由内核发送,也可以由一个进程发送给另一个进程。信号会传递给特定的进程或进程组。
2. 信号的常见用途
- 中断:用户按下 Ctrl+C 会发送
SIGINT信号,通知进程中断。 - 终止进程:可以发送
SIGTERM或SIGKILL来结束进程。 - 控制进程行为:比如使用
SIGSTOP停止进程,使用SIGCONT恢复进程执行。 - 进程间通信:可以使用信号传递简单的信息或请求,通知进程某些事件的发生。
3. 信号的常见类型
以下是 Linux 中的一些常见信号:

| 信号 | 信号编号 | 说明 |
|---|---|---|
SIGINT | 2 | 中断信号(通常由 Ctrl+C 产生) |
SIGTERM | 15 | 终止信号(请求进程终止,优雅退出) |
SIGKILL | 9 | 强制终止信号(无法被捕获或忽略) |
SIGSTOP | 19 | 停止信号(停止进程,无法被捕获) |
SIGCONT | 18 | 恢复进程运行信号(使停止的进程继续) |
SIGSEGV | 11 | 段错误信号(程序访问非法内存时触发) |
SIGALRM | 14 | 定时器到期信号 |
SIGCHLD | 17 | 子进程状态变化信号 |
SIGUSR1 | 10 | 用户定义的信号1(可供用户自定义用途) |
SIGUSR2 | 12 | 用户定义的信号2(可供用户自定义用途) |
4. 信号的处理
进程在接收到信号时,可能会采取以下几种行为:
-
默认行为:操作系统为每个信号提供一个默认的处理方式。例如,
SIGTERM的默认行为是终止进程,SIGSEGV的默认行为是终止进程并生成核心转储文件。 -
忽略信号:进程可以选择忽略某些信号。例如,使用
SIGCHLD时,进程可能选择忽略它,避免子进程终止时通知父进程。 -
自定义处理:进程可以安装一个信号处理函数,处理特定信号。在信号到达时,程序会跳转到该函数进行处理。
5. 信号处理的常用函数
-
signal():设置信号的处理方式,可以是默认行为、忽略或自定义处理函数。
signal(SIGINT, signal_handler);这里,
signal_handler是自定义的信号处理函数,当进程收到SIGINT信号时会执行该函数。 -
sigaction():更强大的信号处理设置,比
signal()更为灵活。可以控制信号的屏蔽、恢复等行为。struct sigaction sa; sa.sa_handler = signal_handler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, NULL);使用
sigaction()可以更精细地控制信号处理行为,例如,指定信号处理时是否屏蔽其他信号。 -
raise():向当前进程发送信号。
raise(SIGINT); // 发送 SIGINT 信号给当前进程 -
kill():向指定的进程发送信号。
kill(pid, SIGTERM); // 向进程 pid 发送 SIGTERM 信号 -
sigprocmask():用于设置或检查信号屏蔽字,进程可以控制哪些信号可以中断其执行。
sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); sigprocmask(SIG_BLOCK, &set, NULL);
6. 信号的屏蔽与阻塞
信号可以在进程中被屏蔽,阻止它们立即执行。使用 sigprocmask() 函数,进程可以选择屏蔽某些信号,直到它们显式解除屏蔽。
例如,进程可以屏蔽 SIGINT 信号,这样按下 Ctrl+C 时不会立即终止程序。
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, NULL); // 屏蔽 SIGINT 信号
7. 信号的发送
信号可以由操作系统、内核或用户程序发送。用户程序可以使用 kill()、raise() 等函数来发送信号:
kill(pid, SIGKILL); // 向进程 pid 发送 SIGKILL 信号
kill()可以向任意进程发送信号。raise()只能向当前进程发送信号。
8. 信号的处理示例
以下是一个使用 signal() 函数处理 SIGINT 信号的简单示例:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void handle_signal(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
// 注册 SIGINT 信号的处理函数
signal(SIGINT, handle_signal);
while (1) {
printf("Running... Press Ctrl+C to send SIGINT\n");
sleep(1); // 模拟长时间运行
}
return 0;
}
在此程序中,按下 Ctrl+C 会触发 SIGINT 信号,进程捕获到信号后会执行 handle_signal 函数。
9. 常见信号的默认行为
| 信号 | 默认行为 |
|---|---|
SIGINT | 终止进程(Ctrl+C) |
SIGTERM | 终止进程(默认的终止信号) |
SIGKILL | 立即终止进程(无法被捕获或忽略) |
SIGSTOP | 停止进程(不能被捕获或忽略) |
SIGCONT | 恢复停止的进程 |
10. 信号的应用场景
-
进程间通信:信号常常用于进程间的简单通信,虽然它比消息队列和管道等方式更简单,但也具有一定的限制。
-
进程控制:信号用于控制进程的行为,比如终止进程、暂停进程等。
SIGKILL是强制终止进程的常用信号,SIGSTOP用于停止进程,SIGCONT用于恢复进程。 -
事件通知:信号常用于通知程序发生某些事件,如定时器到期、外部中断等。
总结
Linux 信号是进程间通信和控制的重要机制,可以用于通知进程发生特定事件或请求进程执行某些操作。理解信号的概念和如何处理它们,对于编写高效、可靠的系统级程序非常重要。