Linux 信号机制是进程间通信(IPC)的一种重要方式,主要用于向进程发送异步通知,使进程可以在特定的事件或条件下作出响应。信号机制本质上是一种软件中断,它可以打断进程的正常执行流,迫使进程去处理特定的事件。
1. 信号的基本概念
信号类型
信号是一种有限的整型值,每个信号都有特定的含义。常见的信号包括:
SIGINT
: 由键盘中断(Ctrl+C)发送,表示进程应被终止。SIGTERM
: 请求终止进程,程序可以捕捉此信号并做相应处理。SIGKILL
: 强制终止进程,无法被捕捉、阻塞或忽略。SIGHUP
: 当控制终端被关闭时发送,常用于通知守护进程重新读取配置。SIGSEGV
: 无效的内存访问(段错误)。SIGSTOP
: 停止进程的执行,进程无法忽略此信号。
信号的特点
- 异步性:信号可以在进程执行任何指令时打断其正常流,进行处理。
- 异步事件:信号通常用于表示异步事件,如硬件中断或外部条件(如定时器到期、用户输入等)。
2. 信号的处理方式
当进程接收到信号时,操作系统会根据进程对该信号的设置,选择适当的处理方式。处理方式主要有以下几种:
1. 默认处理(Default action)
每个信号都有一个默认的处理动作。例如,SIGKILL
的默认动作是终止进程,SIGSEGV
的默认动作是生成 core dump 文件然后终止进程。可以通过 man 7 signal
查看每个信号的默认处理动作。
2. 忽略信号(Ignoring signals)
进程可以选择忽略某些信号。例如:
signal(SIGTERM, SIG_IGN); // 忽略 SIGTERM 信号
3. 捕捉信号(Catching signals)
进程可以设置自定义的信号处理函数来捕捉特定的信号。当信号发生时,操作系统会执行这个处理函数,而不是默认动作。例如:
void handler(int signum) {
printf("Received signal %d\n", signum);
}
int main() {
signal(SIGINT, handler); // 捕捉 SIGINT 信号
while (1) {
sleep(1);
}
return 0;
}
在上面的代码中,handler
是一个自定义的信号处理函数,当用户按下 Ctrl+C
触发 SIGINT
信号时,处理函数会被执行。
4. 阻塞信号(Blocking signals)
在某些情况下,进程可能希望暂时阻塞信号,使其不被处理,直到某个时刻再解除阻塞。可以使用 sigprocmask
函数来阻塞或解除阻塞信号。
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
// 阻塞 SIGINT 信号
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
// 解除对 SIGINT 的阻塞
sigprocmask(SIG_SETMASK, &oldmask, NULL);
3. 信号的生命周期
-
发送信号:信号可以由操作系统、其他进程或本进程自身发送。常用的发送信号的方式有
kill()
、raise()
和alarm()
等。例如:kill(pid, SIGTERM); // 向进程 pid 发送 SIGTERM 信号
-
信号到达:一旦信号被发送,它会进入目标进程的信号队列。信号在进程的某个点被取出并处理。
-
信号处理:处理方式如上所述,取决于进程对该信号的设置。
4. 实现信号处理的注意事项
信号处理程序应尽量简单,因为信号处理函数会打断进程的正常执行。如果信号处理函数执行复杂的操作,可能会引发难以调试的错误。常见的注意事项有:
- 避免在信号处理程序中调用不可重入的函数:例如,
printf()
可能会导致死锁或其他问题,因为它在执行过程中可能会被中断。 - 信号丢失:在 Linux 中,信号不是排队的,某些信号在处理之前可能会被后续信号覆盖。例如,如果在处理某个信号时,新的相同信号到来,旧的信号可能会丢失。因此,对于高频率信号,建议使用
sigaction
而不是signal
来处理。
5. 信号与实时信号
Linux 支持 POSIX 实时信号(Real-Time Signals),其编号从 SIGRTMIN
开始。与标准信号相比,实时信号具有以下特性:
- 信号排队:实时信号不会丢失,内核会将每个信号排入队列。
- 信号优先级:编号越大的实时信号优先级越高。
可以使用