目录
简介
Linux同时支持POSIX可靠信号(以下简称“标准信号)和POSIX实时信号。
信号配置
每个信号都有一个当前配置,它决定了过程在传递信号时的行为。下面给出了一些信号的默认行为:
Term:默认操作是指终止该进程。
Ign:默认操作是忽略该信号。
Core:默认操作是终止进程并转储核心(请参见核心(5))。
Stop:默认操作是要停止该进程。
Cont:默认操作是在当前停止时继续该进程。
应用进程可以通过sigaction(2) 和 signal(2)函数改变信号的默认动作。(signal(2)函数的可移植性较差,具体请参见signal函数说明)
使用这些系统调用,当接收到信号时,应用进程可以选择使用下面的动作:
- 执行默认动作
- 忽略此信号
- 调用信号处理程序捕获信号,这是一个程序员定义的函数在信号传递时自动调用。
默认情况下,将在正常的进程堆栈上调用信号处理程序。可以设置信号处理程序使用一个备用堆栈;有关如何这样做以及什么时候可能有用的讨论,请参阅sigaltstack(2)。
信号配置是进程级别的属性,在多线程的应用中,所有线程的信号处理策略都是一样的。
通过fork创建的子进程继承父进程的信号配置,在调用execve(2)时,信号配置复位为默认值,但是被忽略的信号保持不变,仍然被忽略。
发送信号
以下系统调用和库功能允许调用者发送信号:
raise(3)
发送一个信号给自己,在单线程程序中,相当于:kill(getpid(), sig),在多线程程序中相当于:pthread_kill(pthread_self(), sig);
kill(2)
向指定的进程、指定的进程组的所有成员或系统上的所有进程发送信号。
killpg(2)
向指定进程组中的所有成员发送信号
pthread_kill(3)
以与调用者相同的进程向指定的POSIX线程发送信号。
tgkill(2)
向特定进程中的指定线程发送信号。(这是用于实现pthread_kill(3)的系统调用。)
sigqueue(3)
向指定的进程发送具有伴随数据的实时信号。
捕捉信号
以下系统调用暂停调用进程或线程的执行,直到一个信号被捕获(或未处理的信号终止该进程):
pause(2)
挂死进程直到捕捉到信号。
sigsuspend(2)
暂时更改信号掩码(见下文)并暂停执行,直到捕获一个未屏蔽的信号。
同步接收信号
不同于通过信号处理程序异步捕获一个信号,还可以同步接受信号,即挂起进程直到捕捉到一个信号,此时内核将返回有关调用者的信息。一般有两种方法:
- sigwaitinfo(2), sigtimedwait(2), and sigwait(3) 函数用来挂起进程直到接收到想捕捉的信息. 每个调用都会返回信号相关的信息。
-
signalfd (2)返回一个可用于读取有关的信息的文件描述符,用来获取传递给呼叫者的信号。每次从此文件描述符中读取时进程都会挂起直到在信号fd(2)调用中指定的集合中的一个信号到来。由read(2)返回的缓冲区包含一个结构描述信号。
信号掩码和挂起信号
一个信号可能会被阻塞,这意味着它要到以后才会被传递。在它产生之后到它被处理的时间段被称为是挂起的。
进程中的每个线程都有一个独立的信号掩码,它指示该线程当前正在阻塞的信号集。一个线程可以通过pthread_sigmask (3)函数来设置信号掩码。在传统的单线程应用程序中,可以使用sigprocmask(2)函数来设置信号掩码。
通过fork (2)创建的子节点继承了其父节点的信号掩码的一个副本;这样的信号
掩码在eceve(2)中被保存。
信号有可能对整个进程生效(比如:通过kill函数产生的信号),也有可能是对某个具体的线程生效(例如SIGSEGV 和SIGFPE,由执行特定机器语言指令产生的结果是线程定向的,以及使用pthread_kill (3)针对特定线程的信号)。进程定向信号可以传递到当前没有信号阻塞的任何一个线程。如果多个线程都没有被阻塞,那么内核选择任意线程来传递信号。
一个线程可以通过sigpending(2)函数来获取当前被挂死的一组信号。该调用会返回一个联合体,一组进程级别被挂死的信号和一组被该线程挂死的信号。
通过fork创建的子进程的挂起信号集为空。调用execve(2)时挂起信号集被保存。
标准信号
Linux支持下面列出的标准信号。有几个信号与结构相关,如值列所示。(值的一列有三个值,第一个值一般对alpha和sparc有效,中间一个值对x86,arm和大部分的别的架构有效,最后一个值对mips架构有效)。“-”表示在对应的架构上没有此信号。
首先是在原始的1-1990年的POSIX标准中描述的信号。
SIGKILL 和 SIGSTOP 无法被捕获、阻塞和忽略。
下面是在SUSv2 和 POSIX.1-2001钟描述的信号:
在Linux 2.2中,SIGSYS, SIGXCPU, SIGXFSZ, 和(在非 SPARC和MIPS架构中) SIGBUS信号会终止进程(并且不产生core文件)。在其他一些UNIX系统中,SIGXCPU和SIGXFSZ的默认行为也是终止进程且不产生core文件。Linux 2.4符合POSIX标准。1-2001对这些信号的要求,终止进程的时候产生一个core文件。
其他信号
(Signal 29 is SIGINFO / SIGPWR on an alpha but SIGLOST on a sparc.)
在POSIX中没有指定SIGEMT。1-2001,但仍然出现在大多数其他UNIX系统上,其默认操作通常是使用核心转储终止进程。
SIGPWR(这在POSIX中没有指定。默认情况下通常被忽略,它出现的其他UNIX系统。
SIGIO(这在POSIX中没有指定。默认情况下会被忽略。
实时信号
Linux支持在POSIX.1b中最初定义的实时扩展信号(现在包含在POSIX.1-2001),支持的实时信号的范围由SIGRTMIN和SIGRTMAX定义。POSIX.1-2001要求一个实现至少支_POSIX_RTSIG_MAX (8)实时信号。
Linux内核支持32个不同的实时信号,编号33到64。然而,glibc内部的POSIX线程占用了2到3个实时信号。因而调整SIGRTMIN的值为34或35。由于可用的实时信号的范围和glibc的线程实现相关(这种变化和内核和glibc相关),事实上,实时信号的范围在UNIX系统中是不同的,程序不应该引用使用硬编码数字的实时信号,而应该总是引用使用符号的实时信号SIGRTMIN+n,并包括适当的(运行时)检查,SIGRTMAX+n不超过SIGRTMAX。
与标准信号不同,实时信号没有预定义的含义:整个实时信号集均由应用程序自定义。
对于未经处理的实时信号的默认操作是终止接收过程。
实时信号的主要特征如下:
-
多个实时信号的实例可以被缓存。相比之下,如果是多个同一标准信号的实例在该信号当前存在时被传递没有被及时处理,最终只有一个实例被缓存。
-
如果信号使用sigqueue (3)发送,则一个伴随值(整数或一个指针)可以与信号一起发送。如果接收进程设置了一个处理程序并且带有SA_SIGINFO标志,然后它可以通过传递的第二个参数siginfo_t结构的si_value字段获取该数据。此外,利用该结构的si_pid和si_uid字段可以获得发送信号的进程的PID和真实用户ID。
-
实时信号按有保证的顺序交付。多个相同类型的实时信号按发送的顺序交付。如果不同的实时信号被发送到一个进程中,它们从最低的信号开始传递-已编号的信号。(即,低编号的信号具有最高的优先级。),相比之下,如果多个标准信号正在等待被处理,它们被处理的顺序是不确定的。
如果标准信号和实时信号都在等待处理,POSIX将没有明确哪个被优先处理。例如,Linux在这种情况下优先考虑标准信号。
按照POSIX的规定,一个应用程序必须保证能够缓存至少_POSIX_SIGQUEUE_MAX (32)个实时信号。然而,linux做了不同的处理,截止到including 2.6.7,Linux对所有进程的排队实时信号的数量施加了一个系统范围的限制。可以通过/proc/sys/内核/rtsig-max文件查看并(特权用户)更改此限制。一个相关的文件,/proc/sys/kernel/rtsig-nr,可以用来找出当前有多少实时信号被缓存。在Linux 2.6.8中,这些/proc接口被RLIMIT_SIGPENDING资源限制所取代,该限制指定了缓存信号的每个用户的限制;详情请参见setrlimit(2)。
异步信号安全函数
信号处理程序函数必须非常小心,因为其他地方的处理可能在程序执行的任意点被中断。POSIX具有“安全功能”的概念。如果信号中断了不安全函数的执行,并且处理程序调用了不安全函数,则该程序的行为未定义。
POSIX.1-2004 (also known as POSIX.1-2001 Technical Corrigendum 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(), and sysconf() 函数, 又增加了下面的函数:
execl()
execv()
faccessat()
fchmodat()
fchownat()
fexecve()
fstatat()
futimens()
linkat()
mkdirat()
mkfifoat()
mknod()
mknodat()
openat()
readlinkat()
renameat()
symlinkat()
unlinkat()
utimensat()
utimes()
通过信号处理程序中断系统调用和库函数
如果系统调用或库函数被信号处理函数阻塞,那么:
- 信号处理函数返回时,系统调用或库函数自动重启;
- 系统调用或库函数返回EINTR,即被中断打断;
这两种行为中发生哪一种取决于接口以及是否使用SA_RESTART标志建立了信号处理程序(参见签名(2))。这些细节在UNIX系统中有所不同;下面是Linux的详细信息。
如果以下接口函数被信号处理函数中断进入阻塞状态,那么如果使用了SA_RESTART标志,在信号处理程序返回后,调用将自动重新启动,否则,调用将失败,错误为EINTR:
- 在慢速设备上调用read(2), readv(2), write(2), writev(2), and ioctl(2) 函数。慢速设备是一种I/O呼叫可能无限期阻塞的设备例如,一个端子、管道或插座。(根据此定义,磁盘并不属于慢速设备)。如果在慢速设备上的I/O操作已经传输了部分数据,然后被信号中断,那么该调用高就返回成功状态,并返回传输的字节数。
- open(2), if it can block (e.g., when opening a FIFO; see fifo(7)).
- wait(2), wait3(2), wait4(2), waitid(2), and waitpid(2).
- Socket interfaces: accept(2), connect(2), recv(2), recvfrom(2), recvmsg(2),
send(2), sendto(2), and sendmsg(2), unless a timeout has been set on the socket
(see below). - File locking interfaces: flock(2) and fcntl(2) F_SETLKW.
- POSIX message queue interfaces: mq_receive(3), mq_timedreceive(3), mq_send(3),
and mq_timedsend(3). - futex(2) FUTEX_WAIT (since Linux 2.6.22; beforehand, always failed with EINTR).
- POSIX semaphore interfaces: sem_wait(3) and sem_timedwait(3) (since Linux
2.6.22; beforehand, always failed with EINTR).
下面的函数无论是否使用了SA_RESTART标志,只要被信号中断,就会返回EINTR:
- Socket interfaces, when a timeout has been set on the socket using setsock‐opt(2): accept(2), recv(2), recvfrom(2), and recvmsg(2), if a receive timeout (SO_RCVTIMEO) has been set; connect(2), send(2), sendto(2), and sendmsg(2), if a send timeout (SO_SNDTIMEO) has been set.
- Interfaces used to wait for signals: pause(2), sigsuspend(2), sigtimedwait(2), and sigwaitinfo(2)
- File descriptor multiplexing interfaces: epoll_wait(2), epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).
- System V IPC interfaces: msgrcv(2), msgsnd(2), semop(2), and semtimedop(2).
- Sleep interfaces: clock_nanosleep(2), nanosleep(2), and usleep(3).
- read(2) from an inotify(7) file descriptor.
- io_getevents(2).
sleep函数被信号打断后永远不会重启,会返回成功并返回剩余多少时间待睡眠。
通过停止信号中断系统调用和库函数
在Linux上,即使没有信号处理程序,某些阻塞接口也会失败于EINTR,进程被一个停止信号停止,然后可以通过SIGCONT信号来恢复。这种行为不受POSIX.1的认可,并且不会发生在其他系统。
The Linux interfaces that display this behavior are:
* Socket interfaces, when a timeout has been set on the socket using setsock‐
opt(2): accept(2), recv(2), recvfrom(2), and recvmsg(2), if a receive timeout
(SO_RCVTIMEO) has been set; connect(2), send(2), sendto(2), and sendmsg(2), if a
send timeout (SO_SNDTIMEO) has been set.
* epoll_wait(2), epoll_pwait(2).
* semop(2), semtimedop(2).
* sigtimedwait(2), sigwaitinfo(2).
* read(2) from an inotify(7) file descriptor.
* Linux 2.6.21 and earlier: futex(2) FUTEX_WAIT, sem_timedwait(3), sem_wait(3).
* Linux 2.6.8 and earlier: msgrcv(2), msgsnd(2).
* Linux 2.4 and earlier: nanosleep(2).