当前位置:嗨网首页>书籍在线阅读

12-使用掩码来等待信号_sigsuspend()

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

22.9 使用掩码来等待信号:sigsuspend()

在解释sigsuspend()的功用之前,先介绍一下它的一种使用场景。在对信号编程时偶尔会遇到如下情况。

1. 临时阻塞一个信号,以防止其信号处理器不会将某些关键代码片段的执行中断。

2. 解除对信号的阻塞,然后暂停执行,直至有信号到达。

为达到这一目的,可能会尝试使用程序清单22-4中代码所示方法。

程序清单22-4:解除阻塞并等待信号的错误做法

568.png 程序清单22-4中代码存在一个问题。假设SIGINT信号的传递发生在第二次调用sigprocmask()之后,调用pause()之前。(实际上,该信号可能产生于执行关键片段期间的任一时刻,仅当解除对信号的阻塞后才会随之而传递。)SIGINT信号的传递将导致对处理器函数的调用,而当处理器返回后,主程序恢复执行,pause()调用将陷入阻塞,直到SIGINT信号的第二个实例到达为止。这有违代码的本意:解除对SIGINT阻塞并等待其第一次出现。

即使在关键片段的起始点(即首次调用sigprocmask())和pause()调用之间产生SIGINT信号的可能性不大,但这确实是上述代码的一处缺陷。这种取决于时间的缺陷是竞态条件(5.1节)的例子之一。通常,竞态条件发生于两个进程或线程共享资源时。然而,此处的竞态条件却发生在主程序和其自身的信号处理器之间。

要避免这一问题,需要将解除信号阻塞和挂起进程这两个动作封装成一个原子操作。这正是sigsuspend()系统调用的目的所在。

569.png sigsuspend()系统调用将以mask所指向的信号集来替换进程的信号掩码,然后挂起进程的执行,直到其捕获到信号,并从信号处理器中返回。一旦处理器返回,sigsuspend()会将进程信号掩码恢复为调用前的值。

调用sigsuspend(),相当于以不可中断方式执行如下操作:

570.png 虽然恢复老的信号掩码乍看起来似乎麻烦,但为了在需要反复等待信号的情况下避免竞态条件,这一做法就至关重要。在这种情况下,除非是在sigsuspend()调用期间,否则信号必须保持阻塞状态。如果稍后需要对在调用sigsuspend()之前遭到阻塞的信号解除阻塞,可以进一步调用sigprocmask()。

若sigsuspend()因信号的传递而中断,则将返回−1,并将errno置为EINTR。如果mask指向的地址无效,则sigsuspend()调用失败,并将errno置为EFAULT。

示例程序

程序清单22-5展示了对sigsuspend()的使用。该程序执行如下步骤。

  • 调用printSigMask()函数(程序清单20-4)来显示进程信号掩码的初始值。
  • 阻塞SIGINT和SIGQUIT信号,并保存原始的进程信号掩码。
  • 为SIGINT和SIGQUIT信号建立相同的处理器函数。该处理器显示一条消息,且若对其调用因SIGQUIT信号的传递而引起,则设置全局变量gotSigquit。
  • 循环执行,直至对gotSigquit进行了设置。每次循环都执行如下步骤。
    • 使用printSigMask()函数显示信号掩码的当前值。
    • 令CPU忙于循环并持续数秒钟,以此来模拟对一个关键片段的执行。
    • 使用printPendingSigs()函数来显示等待信号的掩码(程序清单20-4)。
    • 使用sigsuspend()来解除对SIGINT和SIGQUIT信号的阻塞,并等待信号(如果尚未有信号处于等待状态)。
  • 使用sigprocmask()将进程信号掩码恢复为原始状态,然后再使用printSigMask()来显示信号掩码。

程序清单22-5:使用sigsuspend()

以下shell会话日志所示为程序清单22-5中程序的运行结果示例:

573.png 程序调用sigsuspend()解除了对SIGINT信号的阻塞,还显示了最后一行输出。正是在那一点,调用了信号处理器,并显示了那一行输出。

主程序会继续循环。

574.png 此时按下Control-\,将导致信号处理器去设置gotSigquit标志,并转而引发主程序终止循环。