打电话叫程序起床
当计算机中发生了进程需要知道的事情时,操作系统就会向进程发送信号。比如用户想中断进程或“杀死”进程,或进程企图做一件它不应该做的事情,比如访问受限存储器。
除了在发生错误时使用,有时进程也需要产生自己的信号,比如闹钟信号SIGALRM
。闹钟信号通常由进程的间隔定时器创建。间隔定时器就像一台闹钟:你可以定一个时间,其间程序就会去做其他事情:
尽管程序正忙着做其他事,计时器还是会在后台运行,120秒以后……
…定时器发出SIGALRM信号
当进程收到信号以后就会停止手中一切工作来处理信号。进程在收到闹钟信号以后默认会结束进程,但通常情况下使用定时器不是为了让它帮你“杀死”程序,而是为了利用闹钟信号的处理器去做另一件事:
闹钟信号可以实现多任务。如果需要每隔几秒运行一个任务,或者想限制花费在某个任务上的时间,就可以用闹钟信号让程序打断自己。
不要同时使用alarm()和sleep()。
sleep()
函数会让程序沉睡一段时间。和alarm()
函数一样,它也使用了间隔计时器,因此同时使用这两个函数会发生冲突。
重置信号与忽略信号
你已经见过如何设置自定义信号处理器了,但如果你想还原默认的信号处理器怎么办?signal.h头文件中有一个特殊的符号
SIG_DFL
,它代表以默认方式处理信号。
- catch_signal(SIGTERM, SIG_DFL);
同时,你还可以用
SIG_IGN
符号让进程忽略某个信号。
- catch_signal(SIGINT, SIG_IGN);
在你决定忽略某个信号前一定要慎重考虑,信号是控制进程和终止进程的重要方式,如果忽略了它们,程序就很难停下来。
这里没有蠢问题
问:我能把闹钟定在几分之一秒后响铃吗?
答:可以是可以,但很复杂。需要用另一个函数
setitimer()
,它可以把进程间隔计时器的单位设为几分之一秒。问:具体怎么做?
答:详情请见http://tinyurl.com/3o7hzbm。
问:为什么一个进程只有一个定时器?
答:定时器由操作系统的内核管理,如果一个进程有很多定时器,内核就会变得很慢,因此操作系统需要限制进程能使用的定时器个数。
问:定时器可以实现多任务?太好了,也就是说我能同时做几件事?
答:非也。别忘了,进程在处理信号时会停止一切工作,也就是说一次只能做一件事,稍后会看到如何让代码同时做多件事。
问:重复设置定时器会怎么样?
答:每次调用
alarm()
函数都会重置定时器,也就是说如果把闹钟调到10秒,但过一会儿又把它设为了10分钟,那么闹钟信号10分钟以后才会触发,第一个10秒计时就失效了。
练习
这个程序用来测试用户的数学水平,它要求用户做乘法。程序的结束条件如下:
用户按了Ctrl-C。
回答时间超过5秒。
程序在结束时会显示最终得分并把退出状态设为0。
练习解答
这个程序用来测试用户的数学水平,它要求用户做乘法。程序的结束条件如下:
用户按了Ctrl-C。
回答时间超过5秒。
程序在结束时会显示最终得分并把退出状态设为0。
试驾
为了测试这个程序,你需要多运行几遍。
测试一:按Ctrl-C
第一次先回答几个问题然后按Ctrl-C。
Ctrl-C向进程发送中断信号(SIGINT
),程序显示最终得分然后调用exit()
退出。
测试二:等5秒
第二次,不按Ctrl-C,而是在一个问题出现后等待至少5秒,看看会发生什么。
闹钟信号(SIGALRM
)触发了。程序在等用户输入答案,但用户花了太长时间,定时器信号被发送给了程序。程序马上跳转到times_up()
处理器,先显示了“TIME'S UP!”消息,然后把信号升级为SIGINT
,于是程序显示出了最后的得分。
虽然信号有些复杂,但很好用。信号可以让程序从容结束,而间隔定时器可以帮助处理一些超时任务。
这里没有蠢问题
问:信号按什么顺序发送,程序就会按什么顺序接收吗?
答:如果两个信号发送间隔很短就不会,操作系统会先发送它认为更重要的信号。
问:总是如此吗?
答:取决于你的平台。例如在Cygwin的很多版本中,信号会按发送的顺序接收,但通常不应该做这样的假设。
问:如果一个信号发送了两次,进程都会接收到吗?
答:还是要看情况,在Linux和Mac中,如果一个信号在很短的时间里发送了两次,内核只会发送其中的一个;而在Cygwin中两个信号都会发送,但不应该做这样的假设。
要点
操作系统用信号来控制进程。
程序通常用信号来结束。
进程收到信号后会运行信号处理器。
大部分错误信号的默认处理器会终止程序。
可以用
sigaction()
函数替换处理器。可以用
raise()
向自己发送信号。间隔定时器发送
SIGALRM
信号。
alarm()
函数设置间隔定时器。每个进程只能有一个定时器。
不要同时使用
sleep()
和alarm()
。
kill
命令可以向进程发送信号。
kill -KILL
一定可以终止进程。