signal(7) - Linux 手册页
名称
signal - 信号概述
描述
Linux 支持 POSIX 可靠信号(以下简称“标准信号”)和 POSIX 实时信号。
信号处置
- 每个信号都有一个当前的处置 (disposition),它决定了进程在接收到该信号时的行为。
下表中“动作 (Action)”列中的条目指定了每个信号的默认处置,如下所示:
- Term
默认动作是终止进程。
Ign
默认动作是忽略信号。
Core
默认动作是终止进程并转储核心 (core dump)(参见 core(5))。
停止
默认动作是停止进程。
Cont
默认动作是如果进程当前已停止,则继续运行该进程。
- 进程可以使用 sigaction(2) 或 signal(2) 更改信号的处置。(后者在建立信号处理函数时可移植性较差;详情请参见 signal(2)。)使用这些系统调用,进程可以选择在接收到信号时执行以下行为之一:执行默认动作;忽略信号;或者通过信号处理函数 (signal handler) 捕获信号,该处理函数是一个由程序员定义的函数,在信号送达时自动调用。(默认情况下,信号处理函数在正常的进程栈上调用。可以安排信号处理函数使用备用栈;有关如何操作以及何时有用,请参见 sigaltstack(2)。)
信号处置是进程级的属性:在多线程应用程序中,特定信号的处置对所有线程都是相同的。
通过 fork(2) 创建的子进程会继承父进程信号处置的副本。在 execve(2) 期间,已处理信号的处置将重置为默认值;被忽略信号的处置保持不变。
发送信号
- 以下系统调用和库函数允许调用者发送信号:
- raise(3)
向调用线程发送信号。
kill(2)
向指定进程、指定进程组的所有成员或系统上的所有进程发送信号。
killpg(2)
向指定进程组的所有成员发送信号。
pthread_kill(3)
向调用者同一进程中的指定 POSIX 线程发送信号。
tgkill(2)
向特定进程内的指定线程发送信号。(这是用于实现 pthread_kill(3) 的系统调用。)
sigqueue(3)
向指定进程发送带有附带数据的实时信号。
等待信号被捕获
- 以下系统调用会挂起调用进程或线程的执行,直到捕获到信号(或未处理的信号终止了进程):
- pause(2)
挂起执行,直到捕获到任何信号。
sigsuspend(2)
暂时更改信号掩码(见下文)并挂起执行,直到捕获到未屏蔽信号中的一个。
同步接受信号
- 与其通过信号处理函数异步捕获信号,不如同步接受信号,即阻塞执行直到信号送达,此时内核会向调用者返回有关该信号的信息。通常有两种方法可以做到这一点:
- *
sigwaitinfo(2)、sigtimedwait(2) 和 sigwait(3) 会挂起执行,直到指定集合中的一个信号送达。这些调用中的每一个都会返回有关送达信号的信息。
*
signalfd(2) 返回一个文件描述符,可用于读取有关送达给调用者的信号的信息。对该文件描述符的每次 read(2) 都会阻塞,直到 signalfd(2) 调用中指定的集合中的一个信号送达给调用者。read(2) 返回的缓冲区包含一个描述该信号的结构体。
信号掩码和挂起信号
- 信号可以被阻塞 (blocked),这意味着它在解除阻塞之前不会被送达。信号从生成到送达之间的时间段内,该信号被称为挂起 (pending)。
进程中的每个线程都有一个独立的信号掩码 (signal mask),它指示该线程当前正在阻塞的信号集合。线程可以使用 pthread_sigmask(3) 来操作其信号掩码。在传统的单线程应用程序中,sigprocmask(2) 可用于操作信号掩码。
通过 fork(2) 创建的子进程会继承父进程信号掩码的副本;信号掩码在 execve(2) 期间保持不变。
信号可能为整个进程生成(并因此挂起)(例如,当使用 kill(2) 发送时),也可能为特定线程生成(例如,某些信号,如 SIGSEGV 和 SIGFPE,是作为执行特定机器语言指令的结果而生成的,这些信号是线程定向的;使用 pthread_kill(3) 定向到特定线程的信号也是如此)。进程定向的信号可以送达给当前未阻塞该信号的任何一个线程。如果多个线程均未阻塞该信号,则内核会随机选择一个线程来送达信号。
线程可以使用 sigpending(2) 获取其当前挂起的信号集。该集合将由进程定向挂起信号集与调用线程挂起信号集的并集组成。
标准信号
- Linux 支持下述标准信号。若干信号编号取决于架构,如“值 (Value)”列所示。(如果给出了三个值,第一个通常对 alpha 和 sparc 有效,中间的对 x86、arm 和大多数其他架构有效,最后一个对 mips 有效。(未显示 parisc 的值;有关该架构的信号编号,请参见 Linux 内核源代码。- 表示该信号在对应架构上不存在。))
首先是原始 POSIX.1-1990 标准中描述的信号。
- SIGKILL 和 SIGSTOP 信号不能被捕获、阻塞或忽略。
接下来是不在 POSIX.1-1990 标准中,但在 SUSv2 和 POSIX.1-2001 中描述的信号。
- 在 Linux 2.2 及更早版本中,SIGSYS、SIGXCPU、SIGXFSZ 和(在 SPARC 和 MIPS 之外的架构上)SIGBUS 的默认行为是终止进程(不进行核心转储)。(在某些其他 UNIX 系统上,SIGXCPU 和 SIGXFSZ 的默认动作也是终止进程且不进行核心转储。)Linux 2.4 符合 POSIX.1-2001 对这些信号的要求,即终止进程并生成核心转储。
接下来是其他各种信号。
- (信号 29 在 alpha 上是 SIGINFO / SIGPWR,但在 sparc 上是 SIGLOST。)
SIGEMT 未在 POSIX.1-2001 中指定,但仍出现在大多数其他 UNIX 系统上,其默认动作通常是终止进程并生成核心转储。
SIGPWR(未在 POSIX.1-2001 中指定)在出现该信号的其他 UNIX 系统上通常默认被忽略。
SIGIO(未在 POSIX.1-2001 中指定)在其他几个 UNIX 系统上默认被忽略。
在定义了该信号的架构上,SIGUNUSED 在大多数架构上与 SIGSYS 同义。
实时信号
- Linux 支持最初在 POSIX.1b 实时扩展中定义(现已包含在 POSIX.1-2001 中)的实时信号。支持的实时信号范围由宏 SIGRTMIN 和 SIGRTMAX 定义。POSIX.1-2001 要求实现至少支持 _POSIX_RTSIG_MAX (8) 个实时信号。
Linux 内核支持 32 个不同的实时信号,编号从 33 到 64。然而,glibc 的 POSIX 线程实现内部使用了两个(对于 NPTL)或三个(对于 LinuxThreads)实时信号(参见 pthreads(7)),并适当地调整了 SIGRTMIN 的值(调整为 34 或 35)。由于可用实时信号的范围根据 glibc 线程实现的不同而变化(这种变化可能根据可用的内核和 glibc 在运行时发生),且事实上实时信号的范围在 UNIX 系统间也存在差异,因此程序绝对不应使用硬编码的数字引用实时信号,而应始终使用 SIGRTMIN+n 的表示法,并包含适当的(运行时)检查以确保 SIGRTMIN+n 不超过 SIGRTMAX。
与标准信号不同,实时信号没有预定义的含义:整套实时信号均可用于应用程序定义的用途。
未处理的实时信号的默认动作是终止接收进程。
实时信号通过以下特征进行区分:
- 1.
实时信号的多个实例可以排队。相比之下,如果一个标准信号的多个实例在当前被阻塞时送达,则仅有一个实例被排队。
2.
如果使用 sigqueue(3) 发送信号,则可以随信号发送一个附带值(整数或指针)。如果接收进程使用 sigaction(2) 的 SA_SIGINFO 标志为该信号建立了处理函数,则它可以通过传递给处理函数的第二个参数 siginfo_t 结构体中的 si_value 字段获取该数据。此外,该结构体中的 si_pid 和 si_uid 字段可用于获取发送信号进程的 PID 和真实用户 ID。
3.
实时信号按保证的顺序送达。相同类型的多个实时信号按发送顺序送达。如果不同的实时信号被发送给进程,则从编号最小的信号开始送达。(即编号小的信号优先级最高。)相比之下,如果进程有多个标准信号挂起,则它们送达的顺序是不确定的。
- 如果进程同时有标准信号和实时信号挂起,POSIX 没有明确规定哪一个先送达。在此情况下,Linux 与许多其他实现一样,优先处理标准信号。
根据 POSIX,实现应允许至少 _POSIX_SIGQUEUE_MAX (32) 个实时信号排队给一个进程。然而,Linux 的做法有所不同。在 2.6.7 及更早的内核中,Linux 对所有进程的排队实时信号数量设置了系统范围的限制。该限制可以通过 /proc/sys/kernel/rtsig-max 文件查看,并(在拥有权限的情况下)进行修改。相关文件 /proc/sys/kernel/rtsig-nr 可用于查找当前排队的实时信号数量。在 Linux 2.6.8 中,这些 /proc 接口被 RLIMIT_SIGPENDING 资源限制取代,它指定了每个用户的排队信号限制;详情参见 setrlimit(2)。
异步信号安全函数
- 信号处理函数必须非常谨慎,因为程序执行过程中其他地方的处理可能会在任何任意点被中断。POSIX 引入了“安全函数”的概念。如果信号中断了不安全函数的执行,并且 handler 调用了不安全函数,则程序的行为是未定义的。
POSIX.1-2004(也称为 POSIX.1-2001 技术勘误 2)要求实现保证在信号处理函数内部可以安全地调用以下函数:
-
_Exit() _exit() abort() accept() access() aio_error() aio_return() aio_suspend() alarm() bind() cfgetispeed() cfgetospeed() cfsetispeed() cfsetospeed() chdir() chmod() chown() clock_gettime() close() connect() creat() dup() dup2() execle() execve() fchmod() fchown() fcntl() fdatasync() fork() fpathconf() fstat() fsync() ftruncate() getegid() geteuid() getgid() getgroups() getpeername() getpgrp() getpid() getppid() getsockname() getsockopt() getuid() kill() link() listen() lseek() lstat() mkdir() mkfifo() open() pathconf() pause() pipe() poll() posix_trace_event() pselect() raise() read() readlink() recv() recvfrom() recvmsg() rename() rmdir() select() sem_post() send() sendmsg() sendto() setgid() setpgid() setsid() setsockopt() setuid() shutdown() sigaction() sigaddset() sigdelset() sigemptyset() sigfillset() sigismember() signal() sigpause() sigpending() sigprocmask() sigqueue() sigset() sigsuspend() sleep() sockatmark() socket() socketpair() stat() symlink() sysconf() tcdrain() tcflow() tcflush() tcgetattr() tcgetpgrp() tcsendbreak() tcsetattr() tcsetpgrp() time() timer_getoverrun() timer_gettime() timer_settime() times() umask() uname() unlink() utime() wait() waitpid() write()
- POSIX.1-2008 从上述列表中删除了 fpathconf()、pathconf() 和 sysconf(),并添加了以下函数:
-
execl() execv() faccessat() fchmodat() fchownat() fexecve() fstatat() futimens() linkat() mkdirat() mkfifoat() mknod() mknodat() openat() readlinkat() renameat() symlinkat() unlinkat() utimensat() utimes()
信号处理函数对系统调用和库函数的干扰
- 如果信号处理函数在系统调用或库函数调用被阻塞时被调用,则:
- *
该调用在信号处理函数返回后自动重启;或者
*
该调用失败并返回错误 EINTR。
- 这两种行为中的哪一种会发生取决于接口,以及信号处理函数是否使用 SA_RESTART 标志建立(参见 sigaction(2))。详细信息在不同的 UNIX 系统间有所不同;下面是 Linux 的详细信息。
如果对以下接口之一的阻塞调用被信号处理函数中断,则如果使用了 SA_RESTART 标志,调用将在信号处理函数返回后自动重启;否则调用将失败并返回错误 EINTR:
- *
“慢”设备上的 read(2)、readv(2)、write(2)、writev(2) 和 ioctl(2) 调用。“慢”设备是指 I/O 调用可能会无限期阻塞的设备,例如终端、管道或套接字。(根据此定义,磁盘不是慢设备。)如果慢设备上的 I/O 调用在被信号处理函数中断时已经传输了一些数据,则调用将返回成功状态(通常是已传输的字节数)。
*
open(2),如果它能够阻塞(例如,打开 FIFO 时;参见 fifo(7))。
*
wait(2)、wait3(2)、wait4(2)、waitid(2) 和 waitpid(2)。
*
套接字接口:accept(2)、connect(2)、recv(2)、recvfrom(2)、recvmsg(2)、send(2)、sendto(2) 和 sendmsg(2),除非已在套接字上设置了超时(见下文)。
*
- *
POSIX 消息队列接口:mq_receive(3)、mq_timedreceive(3)、mq_send(3) 和 mq_timedsend(3)。
*
futex(2) FUTEX_WAIT(自 Linux 2.6.22 起;在此之前,始终会因 EINTR 而失败)。
*
POSIX 信号量接口:sem_wait(3) 和 sem_timedwait(3)(自 Linux 2.6.22 起;在此之前,始终会因 EINTR 而失败)。
- 以下接口在被信号处理函数中断后永远不会重启,无论是否使用 SA_RESTART;当被信号处理函数中断时,它们总是会失败并返回错误 EINTR:
- *
套接字接口,当使用 setsockopt(2) 在套接字上设置了超时后:如果已设置接收超时 (SO_RCVTIMEO),则为 accept(2)、recv(2)、recvfrom(2) 和 recvmsg(2);如果已设置发送超时 (SO_SNDTIMEO),则为 connect(2)、send(2)、sendto(2) 和 sendmsg(2)。
*
用于等待信号的接口:pause(2)、sigsuspend(2)、sigtimedwait(2) 和 sigwaitinfo(2)。
*
文件描述符多路复用接口:epoll_wait(2)、epoll_pwait(2)、poll(2)、ppoll(2)、select(2) 和 pselect(2)。
*
System V IPC 接口:msgrcv(2)、msgsnd(2)、semop(2) 和 semtimedop(2)。
*
睡眠接口:clock_nanosleep(2)、nanosleep(2) 和 usleep(3)。
*
从 inotify(7) 文件描述符进行的 read(2)。
*
io_getevents(2).
- sleep(3) 函数在被处理函数中断时也不会重启,但会返回一个成功值:剩余睡眠的秒数。
停止信号对系统调用和库函数的干扰
- 在 Linux 上,即使没有信号处理函数,某些阻塞接口也可能在进程被停止信号停止并随后通过 SIGCONT 恢复后因错误 EINTR 而失败。这种行为未得到 POSIX.1 的认可,在其他系统上也不会发生。
表现出此行为的 Linux 接口有:
- *
套接字接口,当使用 setsockopt(2) 在套接字上设置了超时后:如果已设置接收超时 (SO_RCVTIMEO),则为 accept(2)、recv(2)、recvfrom(2) 和 recvmsg(2);如果已设置发送超时 (SO_SNDTIMEO),则为 connect(2)、send(2)、sendto(2) 和 sendmsg(2)。
*
epoll_wait(2)、epoll_pwait(2)。
*
semop(2)、semtimedop(2)。
*
sigtimedwait(2)、sigwaitinfo(2)。
*
- *
Linux 2.6.21 及更早版本:futex(2) FUTEX_WAIT、sem_timedwait(3)、sem_wait(3)。
*
Linux 2.6.8 及更早版本:msgrcv(2)、msgsnd(2)。
*
Linux 2.4 及更早版本:nanosleep(2)。
符合
POSIX.1,另有说明除外。
参见
kill(1)、getrlimit(2)、kill(2)、killpg(2)、rt_sigqueueinfo(2)、setitimer(2)、setrlimit(2)、sgetmask(2)、sigaction(2)、sigaltstack(2)、signal(2)、signalfd(2)、sigpending(2)、sigprocmask(2)、sigsuspend(2)、sigwaitinfo(2)、abort(3)、bsd_signal(3)、longjmp(3)、raise(3)、pthread_sigqueue(3)、sigqueue(3)、sigset(3)、sigsetops(3)、sigvec(3)、sigwait(3)、strsignal(3)、sysv_signal(3)、core(5)、proc(5)、pthreads(7)、sigevent(7)