我们已经看到我们如何改变一个进程的信号掩码来阻塞或反阻塞选定的信号。我们可以使用这个技术来保护我们不想被一个信号中断的代码关键区域。如果我
们想反阻塞一个信号然后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之间发生,那么我们会有问题。在这个时间间隙里信号的任何发生都会丢失,我们可能不再
看到这个信号,这种情况下pause会无尽地阻塞。这是早期不可靠信号的另一个问题。
为了修正这个问题,我们需要在单个原子操作里同时重置信号掩码和催眠进程的一个方法。这个特性由sigsuspend函数提供。
- #include <signal.h>
- int sigsuspend(const sigset_t *sigmask);
- 错误返回-1,errno被设为EINTR。
进程的信号掩码被设为由sigmask所指的值。然后进程被挂起,直到一个信号被捕获,或一个终止进程的信号发生。如果一个信号被捕获且信号处理器返回,那么sigsuspend返回,而且进程的信号掩码在sigsuspend调用之前设为它的值。
注意这个函数没有成功返回。如果它返回到调用者,它总是返回-1,errno设置为EINTR(表示一个中断的系统调用)。
下面的代码展示了保护代码的一个临界区域免受一个特定信号的正确方法:
注意当sigsuspend返回时,它在调用前设置信号掩码为它的值。在这个例子里,SIGINT信号将被阻塞。因此我们把信号掩码重置为我们早先存储的值(oldmask)。
- #include <signal.h>
- void pr_mask(const char *str);
- static void sig_int(int);
- int
- main(void)
- {
- sigset_t newmask, oldmask, waitmask;
- pr_mask("program start: ");
- if (signal(SIGINT, sig_int) == SIG_ERR) {
- printf("signal(SIGINT) error\n");
- exit(1);
- }
- sigemptyset(&waitmask);
- sigaddset(&waitmask, SIGUSR1);
- sigemptyset(&newmask);
- sigaddset(&newmask, SIGINT);
- /*
- * Block SIGINT and save current signal mask.
- */
- if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
- printf("SIG_BLOCK error\n");
- exit(1);
- }
- /*
- * Critical region of code.
- */
- pr_mask("in critical region: ");
- /*
- * Pause, allowing all signals except SIGUSR1.
- */
- if (sigsuspend(&waitmask) != -1) {
- printf("sigsuspend error\n");
- exit(1);
- }
- pr_mask("after return from sigsuspend: ");
- /*
- * Reset signal mask which unblocks SIGINT.
- */
- if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
- printf("SIG_SETMASK error\n");
- exit(1);
- }
- /*
- * And continue processing...
- */
- pr_mask("program exit: ");
- exit(0);
- }
- static void
- sig_int(int signo)
- {
- pr_mask("\nin sig_int: ");
- }
程序的输出:
$ ./a.out
program start:
in critical region: SIGINT
^C
in sig_int: SIGINT SIGUSR1 (这里的SIGINT是因为信号处理器捕获到SIGINT而设的,而不是从sigprocmask函数里设置而来的。)
after return from sigsuspend: SIGINT
program exit:
我们在调用sigsuspend时把SIGUSR1加到安装的掩码里,以便当信号处理器运行时,我们可以知道掩码确实改变了。我们可以看到当sigsuspend返回时,它把信号掩码回复到调用之前的值。
sigsuspend的另一个用法是等待一个信号处理器来设置一个全局变量。在下面的代码,我们捕获中断信号和退出信号,但想只当退出信号被捕获时唤醒主例程:
- #include <signal.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) {
- printf("signal(SIGINT) error\n");
- exit(1);
- }
- if (signal(SIGQUIT, sig_int) == SIG_ERR) {
- printf("signal(SIGQUIT) error\n");
- exit(1);
- }
- sigemptyset(&zeromask);
- sigemptyset(&newmask);
- sigaddset(&newmask, SIGQUIT);
- /*
- * Block SIGQUIT and save current signal mask.
- */
- if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
- printf("SIG_BLOCK error\n");
- exit(1);
- }
- 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) {
- printf("SIG_SETMASK error\n");
- exit(1);
- }
- exit(0);
- }
程序运行结果:
$ ./a.out
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^\$
为
了支持ISO
C的非POSIX和POSIX.1系统之间的可移植性,我们在一个信号处理器里应该做的唯一的事是给sig_atomic_t类型的变量赋一个值,没有别
的了。POSIX.1走得更远并指定了一列函数,它们可以被一个信号处理器安全调用,但是如果我们这样做,我们的代码可能在非POSIX系统不能正确运行。
作为另一个信号的例子,我们展示信号如何被用来同步一个父进程和子进程。下面的代码展示了8.9节的5个例程TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT和WAIT_CHILD。
- #include <signal.h>
- #include <stdio.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) {
- printf("signal(SIGUSR1) error\n");
- exit(1);
- }
- if (signal(SIGUSR2, sig_usr) == SIG_ERR) {
- printf("signal(SIGUSR2) error\n");
- exit(1);
- }
- 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) {
- printf("SIG_BLOCK error\n");
- exit(1);
- }
- }
- 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) {
- printf("SIG_SETMASK error\n");
- exit(1);
- }
- }
- 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) {
- printf("SIG_SETMASK error\n");
- exit(1);
- }
- }
如果我们想在等待一个信号发生时睡眠,sigsuspend函数是好的(正如我们在前两个例子里展示的那样),但是如果我们在等待时想要调用其它系统函数会如何呢?不幸的是,这个问题没有完美的解决方法,除非我们使用多线程并让一个单独的线程处理信号(12.8节)。
没有线程的话,我们可以做的最好的方式是当信号发生时在信号处理器里设置一个全局变量。例如,如果我们捕获SIGINT和SIGALRM并使用
signal_intr函数安装信号处理器,那么信号将会中断任何阻塞的系统调用。系统很有可能在我们阻塞在一个select函数的调用时发生
(14.5.1节),它从一个慢的设备等待输入。(对于SIGALRM来说尤其是这样,因为我们设置闹钟来避免永远等待输入。)处理这个的代码看起来类似
于:
if (intr_flag) /* flag set by our SIGINT handler */
handle_intr();
if (alrm_flag) /* flag set by our SIGALRM handler */
handle_alrm();
/* signals occuring 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从不返回(除非一些数据被准备好读。)
我们想要做的事是下面的步骤:
1、阻塞SIGINT和SIGALRM;
2、测试这两个全局变量来看是否有信号发生,如果有,处理这种情况;
3、调用select(或者任何其它的系统函数,比如read)并反阻塞这两个信号,作为一个原子操作;
sigsuspend函数只当步骤3为pause操作时对我们有用。
阅读(1438) | 评论(0) | 转发(0) |