Linux信号是内核向进程发送的异步通知机制,用于告知进程发生了特定事件,是进程间通信和进程控制的重要手段。不同的信号对应不同的事件场景,了解这些信号的作用和使用方式,对Linux程序开发和系统运维都有重要意义。
Linux信号的分类
Linux系统中的信号主要分为两类,分别是标准信号(也叫传统信号)和实时信号,二者的特性和使用场景有明显区别。
标准信号
标准信号是Linux早期就支持的信号类型,编号范围通常是1到31,这类信号存在两个典型特点:一是信号可能会丢失,如果同一个标准信号在进程处理前一个相同信号时再次到来,进程可能只会收到一次通知;二是信号的传递没有固定的优先级顺序。
常见的标准信号及其作用如下:
| 信号编号 | 信号名称 | 默认处理方式 | 常见触发场景 |
|---|---|---|---|
| 1 | SIGHUP | 终止进程 | 终端断开连接、守护进程重读配置文件 |
| 2 | SIGINT | 终止进程 | 用户在终端按下Ctrl+C组合键 |
| 3 | SIGQUIT | 终止进程并生成核心转储文件 | 用户在终端按下Ctrl+组合键 |
| 9 | SIGKILL | 终止进程 | 强制杀死进程,进程无法捕获或忽略该信号 |
| 15 | SIGTERM | 终止进程 | 默认的进程终止信号,进程可以捕获并做清理工作 |
| 19 | SIGSTOP | 暂停进程 | 停止进程运行,进程无法捕获或忽略该信号 |
| 20 | SIGTSTP | 暂停进程 | 用户在终端按下Ctrl+Z组合键 |
实时信号
实时信号的编号范围通常是34到64,这类信号是为了弥补标准信号的不足而设计的,具有以下特性:信号不会丢失,相同的实时信号多次发送,进程会收到多次通知;信号的传递按照发送的顺序排队,优先级更高的信号会先被处理。实时信号一般用于需要可靠传递通知的场景,开发者也可以自定义实时信号的作用。
查看系统支持的所有信号
在Linux系统中,可以通过kill命令或者signal相关的系统调用查看当前系统支持的所有信号,以下是常用的查看方式。
使用kill命令查看
执行kill -l命令可以列出系统支持的所有信号名称和编号,示例代码如下:
# 列出所有信号 kill -l # 输出示例(不同系统可能略有差异) # 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP # 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 # 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM # 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP # 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ # 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR # 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 # 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 # 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 # 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 # 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 # 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
程序中获取信号信息
在C语言程序中,可以通过signal.h头文件中的相关定义获取信号信息,以下是一个简单的示例程序,用于打印部分常见信号的信息:
#include <stdio.h>
#include <signal.h>
int main() {
// 打印常见信号的名称和编号
printf("SIGHUP: %dn", SIGHUP);
printf("SIGINT: %dn", SIGINT);
printf("SIGKILL: %dn", SIGKILL);
printf("SIGTERM: %dn", SIGTERM);
printf("SIGCHLD: %dn", SIGCHLD);
return 0;
}
信号的处理方式
进程收到信号后,通常有三种处理方式:
- 默认处理:内核为每个信号预设了默认的处理动作,比如终止进程、暂停进程、忽略信号等,大部分信号的默认处理都是终止进程。
- 忽略信号:进程可以告诉内核,当收到某个信号时不做任何处理,但是SIGKILL和SIGSTOP这两个信号不能被忽略。
- 捕获信号:进程可以注册自定义的信号处理函数,当收到对应的信号时,执行自己编写的处理逻辑,比如清理资源、保存数据等。
以下是一个简单的C语言信号处理示例,捕获SIGINT信号并打印提示信息:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// 自定义信号处理函数
void sigint_handler(int signo) {
printf("n收到SIGINT信号,程序即将退出n");
exit(0);
}
int main() {
// 注册SIGINT信号的处理函数
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
printf("注册信号处理函数失败n");
return 1;
}
printf("程序运行中,按下Ctrl+C会触发SIGINT信号处理n");
// 让程序保持运行,等待信号到来
while (1) {
sleep(1);
}
return 0;
}
注意事项
在实际使用Linux信号时,需要注意以下几点:
- SIGKILL和SIGSTOP信号不能被捕获、忽略或者阻塞,只能按照默认方式处理,因此这两个信号是强制终止和暂停进程的可靠方式。
- 在信号处理函数中,尽量不要调用不可重入的函数,比如
printf、malloc等,避免引发不可预期的问题。 - 不同Linux发行版支持的信号可能略有差异,编写跨平台程序时需要注意信号的兼容性。