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) 发送时),也可能为特定线程生成(例如,某些信号,如 SIGSEGVSIGFPE,是作为执行特定机器语言指令的结果而生成的,这些信号是线程定向的;使用 pthread_kill(3) 定向到特定线程的信号也是如此)。进程定向的信号可以送达给当前未阻塞该信号的任何一个线程。如果多个线程均未阻塞该信号,则内核会随机选择一个线程来送达信号。

线程可以使用 sigpending(2) 获取其当前挂起的信号集。该集合将由进程定向挂起信号集与调用线程挂起信号集的并集组成。

通过 fork(2) 创建的子进程最初拥有一个空的挂起信号集;挂起信号集在 execve(2) 期间保持不变。

标准信号

Linux 支持下述标准信号。若干信号编号取决于架构,如“值 (Value)”列所示。(如果给出了三个值,第一个通常对 alpha 和 sparc 有效,中间的对 x86、arm 和大多数其他架构有效,最后一个对 mips 有效。(未显示 parisc 的值;有关该架构的信号编号,请参见 Linux 内核源代码。- 表示该信号在对应架构上不存在。))

首先是原始 POSIX.1-1990 标准中描述的信号。

SIGKILLSIGSTOP 信号不能被捕获、阻塞或忽略。

接下来是不在 POSIX.1-1990 标准中,但在 SUSv2 和 POSIX.1-2001 中描述的信号。

在 Linux 2.2 及更早版本中,SIGSYSSIGXCPUSIGXFSZ 和(在 SPARC 和 MIPS 之外的架构上)SIGBUS 的默认行为是终止进程(不进行核心转储)。(在某些其他 UNIX 系统上,SIGXCPUSIGXFSZ 的默认动作也是终止进程且不进行核心转储。)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 中)的实时信号。支持的实时信号范围由宏 SIGRTMINSIGRTMAX 定义。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_pidsi_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),除非已在套接字上设置了超时(见下文)。

*

文件锁定接口:flock(2) 和 fcntl(2) F_SETLKW

*

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)。

*

inotify(7) 文件描述符进行的 read(2)。

*

Linux 2.6.21 及更早版本:futex(2) FUTEX_WAITsem_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)

引用自

accept4(2)、aio_suspend(3)、close(2)、dup2(2)、errno(3)、intro(2)、intro(3)、killpg(3)、lsyncd(1)、lxc-kill(1)、pgrep(1)、pmloop(3)、psignal(3)、ptrace(2)、s390_runtime_instr(2)、scanf(3)、sensord(8)、sigblock(2)、sigqueue(2)、sigreturn(2)、skill(1)、spu_run(2)、syscalls(2)、timer_create(2)、timer_getoverrun(2)、truncate(2)、vnstatd(1)