更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞。使用这种技术可以保护不希望由信号中断的代码临界区。如果希望对一个信号解除阻塞,然后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 */ if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); /* window is open */ pause(); /* wait for signal to occur */ /* continue processing */
如果在信号阻塞时将其发送给进程,那么该信号的传递就被推迟直到对它解除了阻塞。对应用程序而言,该信号好像发生在解除对SIGINT的阻塞和pause之间。如果发生了这种情况,或者如果在解除阻塞时刻和pause之间确实发生了信号,那么就产生了问题。因为我们可能不会再见到该信号,所以从这种意义上而言,在此时间窗口(解除阻塞和pause之间)中发生的信号丢失了,这样就使pause永远阻塞。
为了纠正此问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。这种功能是由sigsuspend函数提供的。
#include <signal.h> int sigsuspend( const sigset_t *sigmask ); 返回值:-1,并将errno设置为EINTR
将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
注意,此函数没有成功返回值。如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)。
实例
程序清单10-15显示了保护临界区,使其不被特定信号中断的正确方法。
程序清单10-15 保护临界区不被信号中断
#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: "); }
运行该程序的结果如下:
此结果与《UNIX环境高级编程》中的结果不一致,不同之处在于,in sig_int:SIGINT SIGUSR1。(在处理某一信号的时候会不会把该信号暂时阻塞直到对该信号的信号处理函数返回。)这里的问题与http://www.cnblogs.com/nufangrensheng/p/3516134.html中最后的问题其实是一个性质的问题。我想可能是由于实现版本不同造成的吧。
实例
sigsuspend的另一种应用是等待一个信号处理程序设置一个全局变量。程序清单10-16用于捕捉中断信号和退出信号,但是希望仅当捕捉到退出信号时,才唤醒主例程。
程序清单10-16 用sigsuspend等待一个全局变量被设置
#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 */ { signal(SIGINT, sig_int); signal(SIGQUIT, sig_int); 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); }
注意,程序清单10-16中标记为红色字体的两行在《UNIX环境高级编程》中本来是没有的,但是不加这两行,在我的运行环境下(Red Hat Linux 2.6.18)得不到跟书中一样的运行结果:
书中的运行结果(可以反复键入中断字符):
我的运行结果(键入一次中断字符后,第二次键入中断字符就退出了):
而在程序中加入上面带红色标记的语句后,则可以得到与书中同样的结果。
出现上面现象的原因是:对于书中的UNIX版本:一旦对给定的信号设置了一个动作,那么在调用sigaction(signal函数的实现其实就是调用sigaction)显式地改变它之前,该设置就一直有效。而对于我的版本:在进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值(早期版本)。详情可参考:http://www.cnblogs.com/nufangrensheng/p/3515945.html
实例
可以用信号实现父、子进程之间的同步,这是信号应用的另一个实例。程序清单10-17包含了http://www.cnblogs.com/nufangrensheng/p/3510306.html中提到的五个例程的实现,它们是:TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT和WAIT_CHILD。
程序清单10-17 父子进程可用来实现同步的例程
#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); } 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"); }
其中使用了两个用户定义的信号:SIGUSR1由父进程发送给子进程,SIGUSR2由子进程发送给父进程。
如果在等待信号发生时希望去休眠,则使用sigsuspend函数是非常合适的,但是如果在等待信号期间希望调用其他系统函数,那么将会怎样呢?不幸的是,在单线程环境下对此问题没有妥善的解决方法。如果可以使用多线程,则可专门安排一个线程处理信号。
如果不使用线程,那么我们能尽力做到最好的是,当信号发生时,在信号捕捉程序中对一个全局变量置1.
本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/。