目录
信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。Linux 信号可由如下条件产生:
- 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入 Ctrl + C 通常会给进程发送一个中断信号。
- 系统异常。比如浮点异常和非法内存段访问。
- 系统状态变化。比如 alarm 定时器到期将引起 SIGALRM 信号。
- 运行 kill 命令或调用 kill 函数。
服务器程序必须处理(或至少忽略)一些常见的信号,以免异常终止。
一、Linux 信号概述
1. 发送信号
Linux 下,一个进程给其他进程发送信号的 API 是 kill 函数。其定义如下:
该函数把信号 sig 发送给目标进程,目标进程由 pid 参数指定:
pid 参数 | 含义 |
pid > 0 | 信号发送给 PID 为 pid 的进程 |
pid = 0 | 信号发送给本进程组内的其他进程 |
pid = -1 | 信号发送给除 init 进程外的所有进程,但发送者需要拥有对目标进程发送信号的权限 |
pid < -1 | 信号发送给组 ID 为 -pid 的进程组中的所有成员 |
Linux 定义的信号值都大于 0 ,如果 sig 取值为 0 ,则 kill 函数不发送任何信号。但将 sig 设置为 0 可以用来检测目标进程或进程组是否存在,因为检查工作总是在信号发送之前就执行。不过这种检测方式是不可靠的:
- 一方面由于进程 PID 的回绕,可能导致被检测的 PID 不是我们期望的进程的 PID ;
- 另一方面,这种检测方法不是原子操作。
- 该函数成功时返回 0 ,失败则返回 -1 并设置 errno 。几种可能的 errno 如下所示:
errno | 含义 |
EINVAL | 无效信号 |
EPERM | 该进程没有权限发送信号给任何一个目标进程 |
ESRCH | 目标进程或进程组不存在 |
errno | 含义 |
EINVAL | 无效信号 |
EPERM | 该进程没有权限发送信号给任何一个目标进程 |
ESRCH | 目标进程或进程组不存在 |
2. 信号处理方式
目标进程在收到信号时,需要定义一个接收函数来处理之:
信号处理函数只带有一个整型参数,该参数用来指示信号类型。信号处理函数应该是可重入的,否则很容易引发一些竞态条件。所以在信号处理函数中严禁调用一些不安全的函数。除了用户自定义信号处理函数外,bits/signum.h 头文件中还定义了 信号的两种其他处理方式:SIG_IGN 和 SIG_DEL:
- SIG_IGN:表示忽略目标信号;
- SIG_DFL:表示使用信号的默认处理方式:
- 结束进程(Term);
- 忽略信号(Ign);
- 结束进程并生成核心转储文件(Core);
- 暂停进程 (Stop);
- 继续进程(Cont)。
- 结束进程(Term);
- 忽略信号(Ign);
- 结束进程并生成核心转储文件(Core);
- 暂停进程 (Stop);
- 继续进程(Cont)。
3. Linux 信号
Linux 的可用信号都定义在 bits/signum.h 头文件中,其中包括标准信号和 POSIX 实时信号:
信号 | 起源 | 默认行为 | 含义 |
SIGHUP | POSIX | Term | 控制终端挂起 |
SIGNIT | ANSI | Term | 键盘输入以中断进程(Ctrl + C) |
SIGQUIT | POSIX | Core | 键盘输入使进程退出(Ctrl + \) |
SIGILL | ANSI | Core | 非法指令 |
SIGTRAP | POSIX | Core | 断电陷阱,用于调试 |
SIGABRT | ANSI | Core | 进程调用 abort 函数时生成该信号 |
SIGIOT | 4.2 BSD | Core | 和 SIGABRT 相同 |
SIGBUS | 4.2 BSD | Core | 总线错误,错误内存访问 |
SIGFPE | ANSI | Core | 浮点异常 |
SIGKILL | POSIX | Term | 种植一个进程,该信号不可被捕获或忽略 |
SIGUSRI | POSIX | Term | 用户自定义信号之一 |
SIGSEGV | ANSI | Core | 非法内存段引用 |
SIGUSR2 | POSIX | Term | 用户自定义信号之二 |
SIGPIPE | POSIX | Term | 往读端被关闭的管道或者 socket 连接中写数据 |
SIGALRM | POSIX | Term | 由 alarm 或 setitimer 设置的实时闹钟超时引起 |
SIGTERM | ANSI | Term | 终止进程,kill 命令默认发送的信号就是 SIGTERM |
SIGSTKFLT | Linux | Term | 早期的 Linux 使用该信号来报告数学协处理器栈错误 |
SIGCLD | System Ⅴ | Ign | 和 SIGCHLD 相同 |
SIGCHLD | POSIX | Ign | 子进程状态发生变化(退出或暂停) |
SIGCONT | POSIX | Cont | 启动被暂停的进程(Ctrl + Q),如果目标进程未处于暂停状态,则信号被忽略 |
SIGSTOP | POSIX | Stop | 暂停进程(Ctrl + S),该信号不可被捕获或者忽略 |
SIGTSTP | POSIX | Stop | 挂起进程(Ctrl + Z) |
SIGTTIN | POSIX | Stop | 后台进程试图从终端读取输入 |
SIGTTOU | POSIX | Stop | 后台进程试图往终端输入内容 |
SIGURG | 4.2 BSD | Ign | socket 连接上接收到紧急数据 |
SIGXCPU | 4.2 BSD | Core | 进程的 CPU 使用事件超过其软限制 |
SIGXFSZ | 4.2 BSD | Core | 文件尺寸超过其软限制 |
SIGVTALRM | 4.2 BSD | Term | 与 SIGALRM 类似,不过它只统计本进程用户空间代码的运行时间 |
SIGPROF | 4.2 BSD | Term | 与 SIGALRM 类似,它同时统计用户代码和内核的运行时间 |
SIGWINCH | 4.2 BSD | Ign | 终端窗口大小发生变化 |
SIGPOLL | System Ⅴ | Term | 与 SIGIO 类似 |
SIGIO | 4.2 BSD | Term | IO 就绪,比如 socket 上发生可读、可写事件。因为 TCP 服务器可触发多 SIGIO 的条件很多,故而 SIGIO 无法在 TCP 服务器中使用。SIGIO 信号可用在 UDP 服务器中 |
SIGPWR | System Ⅴ | Term | 对于使用 UPS(Uninterruptable Power Supply)的系统,当电池电量过低时,SIGPWR 信号将被触发 |
SIGSYS | POSIX | Core | 非法系统调用 |
SIGUNUSED | ㅤ | Core | 保留,通常和 SIGSYS 效果相同 |
4. 中断系统调用
如果程序在执行处于阻塞状态的系统调用时接收到信号,并且我们为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且 errno 被设置为 EINTR 。可以使用 sigaction 函数为信号设置 SA_RESTART 标志以自动重启被该信号中断的系统调用。
对于默认行为是暂停进程的信号(比如 SIGSTOP 、SIGTTIN),如果我们没有为它们设置信号处理函数,则它们也可以中断某些系统调用(比如 connect 、epoll_wait)。POSIX 没有规定这种行为,这是 Linux 独有的。
二、信号函数
1. signal 系统调用
要为一个信号设置处理函数,可以使用下面的 signal 系统调用:
- sig:指出要捕获的信号类型;
- _handler:是 _sighandler_t 类型的函数指针,用于指定信号 sig 的处理函数;
- signal 函数成功时返回一个函数指针,该函数指针的类型也是 _sighandler_t 。这个返回值是前一次调用 signal 函数时传入的函数指针, 或者是信号 sig 对应的默认处理函数指针 SIG_DEF(如果是第一次调用 signal 的话);
- signal 系统调用出错时返回 SIG_ERR ,并设置 errno 。
2. sigaction 系统调用
设置信号处理函数的更健壮的接口是如下的系统调用:
- sig:指出要捕获的信号类型;
- act:指定新的信号处理方式
- oact:输出信号先前的处理方式(如果不为 NULL 的话);
- sa_hander 成员:指定信号处理函数;
- sa_mask 成员:设置进程的信号掩码(确切地说是在进程原有信号掩码的基础上增加信号掩码),以指定哪些信号不能发送给本进程;
- sa_mask:信号集 sigset_t(_sigset_t 的同义词)类型,该类型指定一组信号;
- sa_flags 成员:用于设置程序收到信号时的行为:
选项 | 含义 |
SA_NOCLDSTOP | 如果 sigaction 的 sig 参数是 SIGCHLD ,则设置该标志表示子进程暂停时不生成 SIGCHLD 信号 |
SA_NOCLDWAIT | 如果 sigaction 的 sig 参数是 SIGHLD ,则设置该标志标识子进程结束时不产生僵尸进程 |
SA_SIGINFO | 使用 sa_sigaction 作为信号处理函数(而不是默认的 sa_handler),它给进程提供更多相关的信息 |
SA_ONSTACK | 调用由 sigaltstack 函数设置的可选信号栈上的信号处理函数 |
SA_RESTART | 重新调用被信号终止的系统调用 |
SA_NODEFER | 当接收到信号并进入其信号处理函数时,不屏蔽该信号。默认情况下,期望进程在处理一个信号时不再接受到同种信号,否则将引起一些竞态条件 |
SA_RESETHAND | 信号处理函数执行完以后,恢复信号的默认处理方式 |
SA_INTERRUPT | 中断系统调用 |
SA_NOMASK | 同 SA_NODEFER |
SA_ONESHOT | 同 SA_RESETHAND |
SA_STACK | 同 SA_ONSTACK |
选项 | 含义 |
SA_NOCLDSTOP | 如果 sigaction 的 sig 参数是 SIGCHLD ,则设置该标志表示子进程暂停时不生成 SIGCHLD 信号 |
SA_NOCLDWAIT | 如果 sigaction 的 sig 参数是 SIGHLD ,则设置该标志标识子进程结束时不产生僵尸进程 |
SA_SIGINFO | 使用 sa_sigaction 作为信号处理函数(而不是默认的 sa_handler),它给进程提供更多相关的信息 |
SA_ONSTACK | 调用由 sigaltstack 函数设置的可选信号栈上的信号处理函数 |
SA_RESTART | 重新调用被信号终止的系统调用 |
SA_NODEFER | 当接收到信号并进入其信号处理函数时,不屏蔽该信号。默认情况下,期望进程在处理一个信号时不再接受到同种信号,否则将引起一些竞态条件 |
SA_RESETHAND | 信号处理函数执行完以后,恢复信号的默认处理方式 |
SA_INTERRUPT | 中断系统调用 |
SA_NOMASK | 同 SA_NODEFER |
SA_ONESHOT | 同 SA_RESETHAND |
SA_STACK | 同 SA_ONSTACK |
- sa_restorer 成员:已经过时,最好不要使用;
- sigaction 成功时返回 0 , 失败则返回 -1 并设置 errno 。
三、信号集
1. 信号集函数
前文提到,Linux 使用数据结构 sigset_t 来表示一组信号。其定义如下:
由该定义可见,sigset_t 实际上是一个长整型数组,数组的每个元素的每个位表示一个信号。这种定义方式和文件描述符集 fd_set 类似。Linux 提供了如下一组函数来设置、修改、删除和查询信号集:
2. 进程信号掩码
前文提到,我们可以利用 sigaction 结构体的 sa_mask 成员来设置进程的信号掩码。此外,如下函数也可以用于设置或查看进程的信号掩码:
- _set:指定新的信号掩码;
- _oset:输出原来的信号掩码(如果不为 NULL 的话);
- 如果 _set 参数不为 NULL ,则 _how 参数指定设置进程信号掩码的方式:
_how 参数 | 含义 |
SIG_BLOCK | 信的进程信号掩码是其当前值和 _set 指定信号集的并集 |
SIG_UNBLOCK | 信的进程信号掩码是当前值和 ~_set 信号集的交集,因此 _set 指定的信号集将不被屏蔽 |
SIG_SETMASK | 直接将进程信号掩码设置为 _set |
_how 参数 | 含义 |
SIG_BLOCK | 信的进程信号掩码是其当前值和 _set 指定信号集的并集 |
SIG_UNBLOCK | 信的进程信号掩码是当前值和 ~_set 信号集的交集,因此 _set 指定的信号集将不被屏蔽 |
SIG_SETMASK | 直接将进程信号掩码设置为 _set |
- 如果 _set 为 NULL ,则进程信号掩码不变,此时仍然可以利用 _oset 参数来获得进程当前的信号掩码;
- sigprocmask 成功时返回 0 ,失败则返回 -1 并设置 errno 。
3. 被挂起的信号
设置进程信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。如下函数可以获得进程当前被挂起的信号集:
- set:用于保存被挂起的信号集。显然,进程即使多次接收到同一个被挂起的信号,sigpending 函数也只能反映一次。并且,当我们再次使用 sigprocmask 使能该挂起的信号时,该信号的处理函数也只被触发一次;
- sigpending 成功时返回 0 ,失败时返回 -1 并设置 errno 。
四、统一事件源
信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。很显然,信号处理函数需要尽可能快地执行完毕,以确保该信号不被屏蔽(前面提到过,为了避免一些竞态条件,信号在处理期间,系统不会再次触发它)太久。一种典型的解决方案是:把信号的主要处理逻辑放到程序的主循环中,当信号处理函数被触发时, 它只是简单地通知主循环程序接收到信号,并把信号值传递给主循环,主循环再根据接收到的信号值执行目标信号对应的逻辑代码。信号处理函数通常使用管道来将信号“传递”给主循环:信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值。那么主循环怎么知道管道上何时有数据可读呢?这很简单,我们只需要使用 I/O 复用系统调用来监听管道的读端文件描述符上的可读事件。如此一来,信号事件就能和其他 I/O 事件一样被处理,即统一事件源。
很多优秀的 I/O 框架库和后台服务器程序都统一处理信号和 I/O 事件,比如 Libevent I/O 框架库和 xinetd 超级服务。下面给出了统 一事件源的一个简单实现:
五、网络编程相关信号
1. SIGHUP
当挂起进程的控制终端时,SIGHUP 信号将被触发。对于没有控制终端的网络后台程序而言,它们通常利用 SIGHUP 信号来强制服务器重读配置文件。一个典型的例子是 xinetd 超级服务程序。
xinetd 程序在接收到 SIGHUP 信号之后将调用 hard_reconfig 函数(见 xinetd 源码),它循环读取 /etc/xinetd.d/ 目录下的每个子配置文件,并检测其变化。如果某个正在运行的子服务的配置文件被修改以停止服务,则 xinetd 主进程将给该子服务进程发送 SIGTERM 信号以结束它。如果某个子服务的配置文件被修改以开启服务,则 xinetd 将创建新的 socket 并将其绑定到该服务对应的端口上。
2. SIGPIPE
默认情况下,往一个读端关闭的管道或 socket 连接中写数据将引发SIGPIPE信号。我们需要在代码中捕获并处理该信号,或者至少忽略它,因为程序接收到 SIGPIPE 信号的默认行为是结束进程,而我们绝对不希望因为错误的写操作而导致程序退出。引起 SIGPIPE 信号的写操作将设置 errno 为 EPIPE 。
可以使用 send 函数的 MSG_NOSIGNAL 标志来禁止写操作触发 SIGPIPE 信号。在这种情况下,应该使用 send 函数反馈的 errno 值来判断管道或者 socket 连接的读端是否已经关闭。
此外,也可以利用 I/O 复用系统调用来检测管道和 socket 连接的读端是否已经关闭。以 poll 为例,当管道的读端关闭时,写端文件描述符上的 POLLHUP 事件将被触发;当 socket 连接被对方关闭时,socket 上的 POLLRDHUP 事件将被触发。
3. SIGURG
在 Linux 环境下,内核通知应用程序带外数据到达主要有两种方法:一种是 I/O 复用技术,select 等系统调用在接收到带外 数据时将返回,并向应用程序报告 socket 上的异常事件;另外一种方法就是使用 SIGURG 信号。
原创存证 · 本地哈希 · bf1daf6f1922...
算法:SHA-256
时间:2026-10-16
哈希:
bf1daf6f192220ee675a281d84bb3c7d20923e561ab3b36bc0bcd57bebe9b3ac该信息用于辅助证明当前内容版本,不等同于法律公证或版权登记。
- 作者:BeZer0
- 链接:https://blog.bezer0.cn/%E8%80%83%E7%A0%94/3fb0bc13-b7ac-80bd-a44b-d72e5466c12f
- 声明:本文为原创文章,采用 CC BY-NC-SA 4.0 协议,转载请注明作者和原文链接。

