我们已经学习了如何修改进程的信号掩码,实现指定信号的阻塞与接触阻塞。我们可以使用这一技术来保护临界区域的代码(当在这一段区域内执行代码的时候哦,我们不希望被信号中断),但是如果我们想要接触一个信号的阻塞,然后进入暂停状态,等待之前阻塞的信号的出现呢?假设指定的信号是SIGINT,可能会采用如下的方式来实现,但是确实错误的:
sigset_t newmask,oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, ISGINT);
/*block SIGINT and save current signal mask*/
if(sigprocmask(SIG_BLOCK), &newmask, &oldmask) < 0)
err_sys(""SIG_BLOCK error");
/*critical region of code */
/*restore signal mask, which unblocks SIGINT*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys(""SIG_SETMASK error");
/*window is open*/
pause(); /*wait for signal to occur*/
/*continue processing */
如果信号在阻塞期间出现,那么信号的发送将会被延迟直到信号接触阻塞。对于应用来说,这可以看做是信号出现在接触阻塞到pause之间出现,如果发生这种情况,或者是信号真地是出现在接触阻塞与pause之间,那么我们就遇到麻烦了。如何出现在这一事件窗口的时间就都会被丢失,感觉上我们重来没有见到过事件的出现,在这种情况下,函数pause将会永远阻塞。这是早期不可靠信号的另外一个问题。
为了纠正这一错误,我们需要一种方式实现:恢复信号掩码和将进程放到sleep状态在单个原子操作内完成。该特性被函数sigsuspend实现了。
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
Returns:-1 with errno set to EINTR
进程的信号掩码被设置成参数sigmask设置的值,然后将进程挂起,直到捕获到一个信号或者是出现一个信号终止掉程序。如果是捕获到一个信号并且信号处理函数返回,然后函数sigsuspend就会返回,并且进程的信号掩码被设置为其调用函数sigsuspend之前的数值。
注意到该函数并没有成功的返回情况,如果该函数返回到调用进程中,那么就总是返回-1,并且errno被设置为EINTR(表示被中断的系统调用)。
Example
图10.22显示了保护一段临界区代码的正确的方法。
include "apue.h"
static void sig_int(int);
int main(void)
{
sigset_t newmask,oldmask,waitmask;
pr_mask("program start: ");
if(signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/*Block SIGINT and save current signal masK.*/
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
/*Critical region of code.*/
pr_mask("in critical region: ");
/*pause,allowing all signals except SIGUSR1.*/
if(sigsuspend(&waitmask) != -1)
err_sys("sigsuspend error");
pr_mask("after return from sigsuspend: ");
/*reset signal mask which unblocks SIGINT.*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
/*Critical region of code.*/
pr_mask("in critical region: ");
/*pause,allowing all signals except SIGUSR1.*/
if(sigsuspend(&waitmask) != -1)
err_sys("sigsuspend error");
pr_mask("after return from sigsuspend: ");
/*reset signal mask which unblocks SIGINT.*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
/*and continue processing...*/
pr_mask("progam exit: ");
exit(0);
}
static void sig_int(int signo)
{
pr_mask("\nin sig_int: ");
}
Figure 10.22 Protecting a critical region from a signal.
执行效果如下所示:
os@debian:~/UnixProgram/Chapter10$ ./10_22.exe
program start: over
in critical region: SIGINT over
^C
in sig_int: SIGINT SIHUSR1 over
after return from sigsuspend: SIGINT over
progam exit: over
os@debian:~/UnixProgram/Chapter10$
Example
sigsuspend的另一个使用的例子是:等待一个信号处理函数设置一个全局变量。在图10.23所示的程序中,我们同事捕获中断信号以及终止信号,但是想要唤醒主线程只有是在终止信号被捕获到的时候才行。
#include "apue.h"
volatile sig_atomic_t quitflag; /*set nonzero by signal handler*/
static void sig_int(int signo) /*one signal handler for SIGINT and SIGQUIT*/
{
if(signo == SIGINT)
printf("\ninterrupt\n");
else if(signo == SIGQUIT)
quitflag = 1; /*set flag for main loop */
}
int main(void)
{
sigset_t newmask, oldmask, zeromask;
if(signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
if(signal(SIGQUIT, sig_int) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
/*Block SIGQUIT and save current signal mask. */
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
while(quitflag == 0)
{
sigsuspend(&zeromask);
}
/*SIGQUIT has been caught and is now blocked; do whatever*/
quitflag = 0;
/*Reset signal mask which unblocks SIGQUIT*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
exit(0);
}
Figure 10.23 Using sigsuspend to wait for a global variable to be set
执行效果如下所示:
os@debian:~/UnixProgram/Chapter10$ ./10_23.exe
^C type the interrupt character
interrupt
^C type the interrupt character
interrupt
^\os@debian:~/UnixProgram/Chapter10$ now terminate with the quit character
为了程序可以在支持ISO C的非POSIX系统与POSIX.1系统直接移植,我们在信号处理函数中唯一需要做的就是赋值给一个类型为sig_atomic_t类型的变量,在不需要做其他事情了。POSIX.1更加进步,指定了一系列在信号处理函数中可以安全调用的函数(图10.4所示),但是如果我们这样做的话,我们的代码就不能再非POSIX系统上运行。
Example
信号的另一个例子是,展示信号如何用于同步父进程和子进程。图10.24显示了8.9节中提到的五个函数的实现:
- TELL_WAIT
- TELL_PARENT
- TELL_CHILD
- WAIT_PARENT
- WAIT_CHILD
#include "apue.h"
static volatile sig_atomic_t sigflag; /* set nonzero by sig handlerr */
static sigset_t newmask, oldmask, zeromask;
static void sig_usr(int signo) /*one signal handler for SIGUSR1 and SIGUSR2*/
{
sigflag = 1;
}
void TELL_WAIT(void)
{
if(signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if(signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR2) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
/*Block SIGUSR1 and SIGUSR2, and save current signal mask */
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}
void TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
}
void WAIT_PARENT(void)
{
while(sigflag == 0)
{
sigsuspend(&zeromask); /*and wait for parent */
}
sigflag = 0;
/*Reset signal mask to original value*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
err_sys("SIG_SETMASK error");
}
}
void TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1); /*tell child we're done */
}
void WAIT_CHILD(void)
{
while(sigflag == 0)
sigsuspend(&zeromask); /*and wait for child*/
sigflag = 0;
/*Reset signal mask to original value*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
Figure 10.24 允许父进程与子进程进行同步的函数实现
我们使用了两个用户自定义信号:SIGUSR1被父进程发送给子进程,SIGUSR2被子进程发送给父进程,在图15.7中,我们展示了另外一个使用管道实现的例子。
如果我们想要在进入sleep模式的时候还可以等待指定信号的出现的话,sigsuspend函数非常方便,但是如果在我们的等待期间想要调用其他系统函数的话怎么样呢?不幸的是,该问题并没有一个根本的解决办法,除非使用多线程并且使用一个单独的线程来处理信号,正如我们将在12.8节中介绍的那样。
如果不使用线程的话,我们可以实现的最好的方法是当信号出现的时候在信号处理函数中设置一个全局变量,比如说,当我们捕获到信号SIGINT以及SIGALRM并使用signal_intr函数来安装信号处理函数,那么该信号将能够中断任意被阻塞的慢速系统调用。信号很有可能出现在等待一个慢速设备输入的read函数调用过程中, 比如说,我们可以设置一个定时器来防止在一个输入中永远等待,其处理代码看起来如下所示:
if(intr_flag) /*flag set by our SIGINT handler */
handler_intr();
if(alrm_flag) /*flag set by out SIGALRM handler*/
handle_alrm();
/*signals occuring in here are lost */
while((n = read( ... )) < 0)
{
if(errno == EINTR)
{
if(alrm_flag)
handle_alrm();
else if(intr_flag)
handle_intr();
}
else
{
/* some other error. */
}
}
if(n == 0)
{
/*end of file */
}
if(n > 0)
{
/*process input*/
}
上述程序中,我们在调用read函数之前以及read返回一个错误的时候都会检查全局标识。问题是如果信号在两次检查之间被捕获到的话,信号将被丢失,因为对应的信号处理函数被调用,设置完成对应的标识,但是read函数却永远也不会返回(除非有数据读就绪)。
我们想要执行的步骤实际上是这样的:
- 阻塞信号SIGINT以及SIGALRM;
- 测试两个全局标识,检查时候由信号出现,如果是这样的话,就处理对应的条件;
- 调用函数read(或者是其他系统函数),然后解除两个信号的阻塞,而且要求这是一个原子操作;
函数sigsuspend在第三步中只能帮组我们执行系统函数pause(),别的无能为力。