Lazy loaded image
考研
Linux 网络编程学习笔记——十、信号
字数 5373阅读时长≈ 14 分钟
2026-10-16
2026-10-16

目录

信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。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

该信息用于辅助证明当前内容版本,不等同于法律公证或版权登记。


评论
Loading...