Unix信号
在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
信号类似于中断,不同之处在于中断由处理器调解并由内核处理,而信号由内核调解(可能通过系统调用)并由进程处理。内核可以将中断作为信号传递给导致中断的进程(典型的例子有SIGSEGV、SIGBUS、SIGILL和SIGFPE)。
信号起源于20世纪70年代的贝尔实验室Unix,最近在POSIX标准中有所规定。
嵌入式程序可能会发现信号对于进程间通信很有用,因为信号的计算和内存占用很小。
发送信号
- 在一个运行的程序的控制终端键入特定的组合键可以向它发送某些信号:
- kill()系统调用会在权限允许的情况下向进程发送特定的信号,类似地,kill命令允许用户向进程发送信号。
raise(3)
库函数可以将特定信号发送给当前进程。 - 像除数为零、段错误这些异常也会产生信号(这里分别是SIGFPE和SIGSEGV,默认都会导致进程终止和核心转储)。
- 内核可以向进程发送信号以告知它一个事件发生了。例如当进程将数据写入一个已经被关闭的管道时将会收到SIGPIPE信号,默认情况下会使进程关闭。
处理信号
信号处理函数可以通过signal()
系统调用来设置。如果没有为一个信号设置对应的处理函数,就会使用默认的处理函数,否则信号就被进程截获并调用相应的处理函数。在没有处理函数的情况下,程序可以指定两种行为:忽略这个信号(SIG_IGN)或者用默认的处理函数(SIG_DFL)。但是有两个信号是无法被截获并处理的:SIGKILL和SIGSTOP。
风险
因为竞态条件的存在,信号的处理是有弱点的。因为信号是异步的,所以在处理一个信号的过程中,进程可能收到另一个信号(甚至是相同的信号)。sigprocmask()
系统调用可以用来阻塞和恢复信号的传递。信号可以造成进程中系统调用的中断,并在信号处理完后重新开始未完成的系统调用。信号处理函数应该没有任何不想要的副作用,比如,errno的改变、信号掩码的改变、信号处理方法的改变,以及其他全局进程性质的改变。在信号处理函数内使用不可重入函数,如malloc和printf,也是不安全的。
与硬件异常的关系
进程的运行也可能导致硬件异常,例如,将一个数除以零,或者出现TLB不命中。在类Unix系统中,这会自动运行内核的异常处理程序。对于某些异常如页缺失,内核有足够的信息来处理完并恢复进程的运行。但是对于另外一些异常,内核不能处理而只能通过发送信号把异常交给进程自己处理。例如在x86架构的CPU上,如果一个进程尝试将一个数除以零,将会产生divide error异常,并使内核向出错的进程发送SIGFPE信号。相似地,如果一个进程尝试访问虚拟地址空间以外的内存,内核将向进程发送SIGSEGV信号。异常与信号的具体对应关系在不同的CPU架构上是不同的。
信号列表
单一UNIX规范规定了在<signal.h>中定义的信号有:
备注:打星号的部分表示这是X/Open System Interfaces (XSI)扩充的部分。使用引号的文字是引用自SUS[1] (页面存档备份,存于互联网档案馆)。
- SIGABRT 和 SIGIOT
SIGABRT 和 SIGIOT 信号能让程序异常终止(abort)。 该信号通常是由进程自身调用 C标准函数库 的 abort()
函数来触发, 但它也可以像其它信号一样由外部发送给进程。
- SIGALRM, SIGVTALRM 和 SIGPROF
如果你用 setitimer 这一类的报警设置函数设置了一个时限,到达时限时进程会接收到 SIGALRM, SIGVTALRM 或者 SIGPROF。但是这三个信号量的含义各有不同,SIGALRM 计时的是真实时间,SIGVTALRM计时的是进程使用了多少CPU时间,而 SIGPROF 计时的是进程和代表该进程的内核用了多少时间。
- SIGBUS
总线发生错误时,进程接收到一个SIGBUS信号。举例来说,存储器访问对齐或者或不存在对应的物理地址都会产生SIGBUS信号。
- SIGCHLD
当子进程终止、被中断或被中断后恢复时,SIGCHLD信号被发送到进程。该信号的一个常见用法是指示操作系统在子进程终止后清理其使用的资源,而不显式调用等待系统调用。
- SIGCONT
SIGCONT信号指示操作系统继续(重启)先前由SIGSTOP或SIGTSTP信号暂停的进程。Unix 壳的作业控制是该信号的一个重要应用。
- SIGFPE
当进程执行了一个错误的算术运算时,例如被零除,信号被发送到一个进程。这可能包括整数被零除,以及整数在除结果中溢出(在C中只有INT_MIN/-1、INT64_MIN/-1和%-1会触发该行为)。注意该信号与浮点数溢出无关。
- SIGHUP
检测到控制中断挂起或者控制进程死亡时,进程会收到 SIGHUP。现在操作系统,该信号通常意味着使用的 虚拟终端 已经被关闭。许多 守护进程 在接收到该信号时,会重载他们的设置和重新打开日志文件(logfiles),而不是去退出程序。nohup 命令用于无视该信号。
- SIGILL
当进程试图执行非法、格式错误、未知或特权指令时,SIGILL信号被发送到该进程。
- SIGINT
当用户希望中断进程时,SIGINT信号由用户的控制终端发送到进程。这通常通过按下Ctrl+C来发送,但是在某些系统中,可以使用“DELETE”键或“BREAK”键。
- SIGKILL
发送SIGKILL信号到一个进程可以使其立即终止(KILL)。与SIGTERM和SIGINT相不同的是,这个信号不能被捕获或忽略,接收过程在接收到这个信号时不能执行任何清理。以下例外情况适用:
- 僵尸进程不能被杀死,因为它们已经死了,正在等待它们的父进程来收获它们。
- 处于阻塞状态的进程不会死亡,直到它们再次醒来。
- init 进程是特殊的: init不接收任何它不打算处理的信号,因此它会忽略SIGKILL。[1]这条规则有一个例外,Linux 上的 init 如果被 ptrace 了,那么它是可以接收 SIGKILL 并被杀死的[2][3]。
- 处于不可中断的睡眠的进程即使发送了SIGKILL,也有可能不会终止(并释放其资源)。这是少数 Unix 系统必须重新启动才能解决临时软件问题的几种情况之一。
当在大多数系统关闭程序中终止进程时,如果进程没有响应 SIGTERM 而自动退出,SIGKILL 是最后的手段。为了加快电脑关机过程,Mac OS X 10.6向标记自己为“clean”的进程发送SIGKILL,从而加快关机时间,而且可能不会产生任何不良影响。[4]。在 Linux 中执行 killall -9
命令具有类似不过更危险的效果;它不让程序保存未保存的数据。
- SIGPIPE
当一个进程试图写入一个没有连接到另一端进程的管道时,SIGPIPE信号会被发送到该进程。
- SIGPOLL
当一个事件发生在一个正在显式监视的文件描述符上时,就会发送SIGPOLL信号。有效使用这种用法可以进行异步 I/O,因为内核将代替调用者轮询描述符。它提供了主动轮询的替代方案。
- SIGRTMIN 到 SIGRTMAX
SIGRTMIN至SIGRTMAX信号用于用户自定义的目的。它们是实时信号。
- SIGTTIN 和 SIGTTOU
当进程在后台试图分别从tty读取或写入时,SIGTTIN和SIGTTOU信号会被发送到该进程。通常,这些信号仅由作业控制下的进程接收;守护进程没有控制终端,因此永远不会接收这些信号。
- SIGQUIT
当用户在进程的控制终端请求退出进程并进行核心转储时,SIGQUIT信号会被发送到该进程。
- SIGSEGV
当进程试图访问无效内存引用时发生存储器区段错误,SIGSEGV信号会被发送到该进程。
- SIGSTOP
当操作系统暂停进程的运行时,会产生SIGSTOP信号。SIGSTOP信号无法被捕获或无视。
- SIGSYS
当系统调用时传入非法的参数,会产生SIGSYS信号。实际上,SIGSYS信号很少会出现,因为应用程序依赖库调用系统调用。SIGSYS可以被违反Linux Seccomp安全规则的应用程序捕获。SIGSYS也可用于模拟外部系统调用,例如:在Linux上模拟 Windows系统调用。
- SIGTERM
当用户请求终止进程时,会产生SIGTERM信号。SIGTERM信号可以被捕获或无视。这允许该进程在结束前释放掉所占用的资源并保存其状态。SIGINT和SIGTERM非常相似。
- SIGTSTP
当用户在进程的控制终端请求退出进程时,会产生SIGTSTP信号。SIGTSTP信号可以被捕获或无视。SIGTSTP信号的产生通常是由于用户按下Ctrl+Z。
默认行为
一个进程可以自定义如何处理传入的POSIX信号。如果一个进程没有定义一个信号处理程序,那么这个信号的默认处理程序将被使用。下表列出了一些与POSIX兼容的UNIX系统的默认操作,例如FreeBSD、OpenBSD和Linux。
信号 | 可移植代号 | 默认行为 | 描述 |
---|---|---|---|
SIGABRT | 6 | 终止 (核心转储) | 进程终止信号 |
SIGALRM | 14 | 终止 | 计时器告警 |
SIGBUS | 不适用 | 终止 (核心转储) | 访问内存对象未定义区域 |
SIGCHLD | 不适用 | 忽略 | 子进程终止、暂停、继续 |
SIGCONT | 不适用 | 继续 | 如果被暂停,重新继续执行 |
SIGFPE | 8 | 终止 (核心转储) | 错误的算术运算 |
SIGHUP | 1 | 终止 | 挂起 |
SIGILL | 4 | 终止 (核心转储) | 非法的指令 |
SIGINT | 2 | 终止 | 终端中断信号 |
SIGKILL | 9 | 终止 | 杀死 (无法被捕获或忽略的信号) |
SIGPIPE | 13 | 终止 | 写入一个没有连接另一端的管道 |
SIGPOLL | 不适用 | 终止 | 可轮询事件 |
SIGPROF | 不适用 | 终止 | 性能调优定时器超时 |
SIGQUIT | 3 | 终止 (核心转储) | 终端退出信号 |
SIGSEGV | 11 | 终止 (核心转储) | 非法的内存引用 |
SIGSTOP | 不适用 | 暂停 | 暂停执行(无法被捕获或忽略的信号) |
SIGSYS | 不适用 | 终止 (核心转储) | 错误的系统调用 |
SIGTERM | 15 | 终止 | 终止信号 |
SIGTRAP | 5 | 终止 (核心转储) | 追踪/断点陷阱 |
SIGTSTP | 不适用 | 暂停 | 终端中止信号 |
SIGTTIN | 不适用 | 暂停 | 后台进程尝试读 |
SIGTTOU | 不适用 | 暂停 | 后台进程尝试写 |
SIGUSR1 | 不适用 | 终止 | 用户自定义信号1 |
SIGUSR2 | 不适用 | 终止 | 用户自定义信号2 |
SIGURG | 不适用 | 忽略 | Out-of-band data is available at a socket |
SIGVTALRM | 不适用 | 终止 | 虚拟定时器超时 |
SIGXCPU | 不适用 | 终止 (核心转储) | 超出CPU时间限制 |
SIGXFSZ | 不适用 | 终止 (核心转储) | 超出文件大小限制 |
SIGWINCH | 不适用 | 忽略 | 终端窗口大小已变化 |
- 可移植编号
对于大多数信号,相应的信号编号由实现定义。此列列出了POSIX标准中指定的数字。
- 行为释义
- 终止 – 进程异常终止。进程终止的结果和调用 _exit() 是一样的,除了终止可以向 wait() 和 waitpid() 返回导致进程终止的信号。
- 终止(核心转储) – 进程异常终止。这种进程中止的过程根据实现有所不同,一般会创建一个核心文件。
- 忽略 – 进程忽略该信号。
- 暂停 – 进程被暂停(不是终止)。
- 继续 – 进程恢复执行。
参考文献
- ^ https://manpages.ubuntu.com/manpages/zesty/man2/kill.2.html (页面存档备份,存于互联网档案馆) section NOTES
- ^ SIGKILL init process (PID 1). Stack Overflow.
- ^ Can root kill init process?. Unix & Linux Stack Exchange.
- ^ Mac Dev Center: What's New in Mac OS X: Mac OS X v10.6. 2009-08-28 [18 November 2017]. (原始内容存档于2010-01-14).
外部链接
- Introduction to Unix Signals Programming
- Another Introduction to Unix Signals Programming (页面存档备份,存于互联网档案馆)
- UNIX and Reliable POSIX Signals (页面存档备份,存于互联网档案馆) by Baris Simsek
- Signal Handlers (页面存档备份,存于互联网档案馆) by Henning Brauer
参见
<signal.h>