Signal
信号(Signal)是进程间通信的一种简单的方式。但是显然,你要为你的多进程应用做进程通信你肯定不会使用信号来做的。发往进程的诸多信号,通常都是源于内核。
信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。所以信号又被称为软中断。
特别为我们熟悉的信号是SIGINT,当用户键入终端中断字符(通常为 Control-C)时,终端驱动程序将发送该信号给前台进程组。该信号的默认行为是终止进程。还有SIGKILL ,此信号为“必杀(sure kill)”信号,处理器程序无法将其阻塞、忽略或者捕获,总能终止进程。
Signal Handling
Linux提供了sigaction()
系统调用,允许我们修改某些Signal处理的默认行为。
一般而言,将信号处理器函数设计得越简单越好。其中的一个重要原因就在于,这将降低引发竞争条件的风险。下面是针对信号处理器函数的两种常见设计。回顾一下OS课中,内核是如何处理中断的?他要保存用户状态到Stack中,然后在和用户态完全分离的内核态中进行处理。但是现在在处理Signal的时候,我们都在用户态,数据并没有被隔离——难道因为要处理信号,我就要多这么多心里负担吗——而且信号是随时出现的。
在信号处理器函数中,并非所有系统调用以及库函数均可予以安全调用。
更新全局变量或静态数据结构的函数可能是不可重入的。(只用到本地变量的函数肯定是可重入的。)如果对函数的两个调用(例如:分别由两条执行线程发起)同时试图更新同一全局变量或数据类型,那么二者很可能会相互干扰并产生不正确的结果。在信号处理函数中,我们应该尽可能只调用可重入函数。
printf
是一个不可重入函数的典型例子,他使用了静态数据结构。所以如果我们在信号处理函数中不应该使用printf
。如果主程序和处理函数都用了,就有发生冲突的可能。
所有的信号都会通过同一个函数进行处理,进程能够使用 sigpending()
系统调用来获取等待信号集。
一般来说,我们无须为我们的程序编写信号处理函数。如果希望程序优雅退出,可以编写SIGINT的处理函数。
Advanced
Programming in other languages
这部分内容可以参考CLI中的介绍。