sigsuspend函数
我们已经见过如何对进程的信号掩码进行阻塞和解锁。我们可以通过这种技术对不想被信号中断的代码段进行保护。如果我们想解锁一个信号然后pause,等待之前阻塞的信号再次发生会怎么样?假设我们等待的信号是SIGINT,不正确的方法是:
sigset_t newmask, oldmask;
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 */
/* reset 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()会永久的阻塞程序。对于早期不可靠的信号还有其它的问题。
正确解决这个问题,我们须要即重置信号掩码又能让进程休眠的原子操作。这个功能由sigsuspend函数提供。
|
进程的信号掩码由sigmask参数指向。然后进程被挂起直到一个信号被捕获或者直到一个信号终止了进程。如果一个信号被捕获并且该信号的信号处理函数返回,之后sigsuspend返回,并且进程的信号掩码被设置成调用sigsuspend之前的值。
注意:这个函数执行成功后没有返回值。如果返回到调用者,它一直都是返回1,并errno设置成EINTR(指明中断了一个系统调用)
例子:
Figure10.22显示了一个正确处理防止重要代码段被特定信号中断的例子。
注意,当sigsuspend返回时,它设置进程的信号掩码是调用sigsuspend函数之前的值。在本例中阻塞SIGINT信号。因此我们重置信号掩码到之前保存的值(oldmask)。
运行Figure10.22的输出如下:
$ ./a.out
program start:
in critical region: SIGINT
^? type the interrupt character
in sig_int: SIGINT SIGUSR1
after return from sigsuspend: SIGINT
program exit:
|
当我们调用sigsuspend时,添加了SIGUSR1到信号掩码。所以当信号处理函数运行时,我们能告之信号掩码实际上已经更改了。当sigsuspend返回时我们能看到它恢复了信号掩码到调用sigsuspend之前的值。
Figure 10.22. Protecting a critical region from a signal#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");
/*
* And continue processing ...
*/
pr_mask("program exit: ");
exit(0);
}
static void
sig_int(int signo)
{
pr_mask("\nin sig_int: ");
|
例子
另一种使用sigsuspend的方法是等待信号处理函数设置全局变量。在Figure10.23的程序中我们捕获了中断信号和退出信号,但是我们想在捕获退出信号时唤醒主函数。
该程序的简单输出是:
$ ./a.out
^? type the interrupt character
interrupt
^? type the interrupt character again
interrupt
^? and again
interrupt
^? and again
interrupt
^? and again
interrupt
^? and again
interrupt
^? and again
interrupt
^\ $ now terminate with quit character
|
Figure 10.23. Using sigsuspend to wait for a global variable to be set#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);
}
|
为了在支持ISO C的非POSIX系统和POSIX.1系统间的兼容性,我们只要在信号处理函数中做一件事情,那就是给变量添加一个sig_atomic_t的限定符,其它的什么也不用做。在Figure10.4这个函数列表中的函数可以安全的运行在信号函数函数中,但是如果我们使用了sig_atomic_t的话,我们的代码就可能在非POSIX的系统上正确运行。
|
例子
另一个信号的例子,我们显示了信号曾经能用于同步父子进程。Figure10.24显示了8.9节中五个例程的实现:TELL_WAIT,TELL_PARENT,TELL_CHILD,WAIT_PARENT和WAIT_CHILD。
我们使用两个用户定义信号:SIGUSR1信号是用来从父进程发向子进程的,SIGUSR2是由子进程发往父进程的。在之前Figure15.7中显示了这五个函数人使用管道的实现。
Figure 10.24. Routines to allow a parent and child to synchronize#include "apue.h"
static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
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");
}
|
如果我们想让进程休眠并等待信号发生sigsuspend函数很适合,但是如果我们想在等待的同时调用其它系统函数会发生什么?很不幸,我们没有解决方法,除非使用多线程时让一个单独的线程处理信号,我们将会在APUE12.8节看到。
在没有使用线程时,我们最好是在当信号发生时在信号处理函数中设置一个全局变量。例如:如何我们要捕获SIGINT和SIGALRM,并且使用signal_intr函数安装信号处理函数,信号会中断任何慢系统调用,使其被阻塞。信号多数会发生在当我们被阻塞在select函数中等待一个慢设备输入时。(特别是对于SIGALRM,因为我们设置一个警告时钟防止我们一直等待那个输入。)代码看起来像如下:
if (intr_flag) /* flag set by our SIGINT handler */
handle_intr();
if (alrm_flag) /* flag set by our SIGALRM handler */
handle_alrm();
/* signals occurring in here are lost */
while (select( ... ) < 0) {
if (errno == EINTR) {
if (alrm_flag)
handle_alrm();
else if (intr_flag)
handle_intr();
} else {
/* some other error */
}
}
|
如果select返回一个系统调用错误,那么我们在调用select之前再次测试全局标记。如果信号在前两个if和随后的select之间就会有问题发生。已经在程序的注释处写明。信号处理函数被调用,并且它们设置适当的全局函数,但是select绝对不会返回(除非有一些数据准备读取)。
下面的步骤是我们应该能去做的,顺序是:
- 阻塞SIGINT和SIGALRM。
- 测试两个全局变量,并查看它们是否某个信号已经发生了,如果发生了,处理这个条件。
- 调用select(或任何其它系统函数,类似read)并解锁两个信号,类似一个原子操作。
只有当第三步是一个pause操作时,sigsuspend函数才对我们有用。(The sigsuspend function helps us only if step 3 is a pause operation.)