函数sigsuspend,在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。
#include <signal.h> int sigsuspend(const sigset_t *sigmask); // 返回值:-1,并将errno设置为EINTR
将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
注意,此函数没有成功返回值。如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)。
程序清单10-15显示了保护临界区,使其不被特定信号中断的正确方法。
《UNIX环境高级编程》P269:程序清单10-15 保护临界区不被信号中断(有改动)
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <errno.h> static void sig_int(int signo); void pr_mask(const char *str); int main(void) { sigset_t newmask, oldmask, waitmask; pr_mask("program start: "); if (signal(SIGINT, sig_int) == SIG_ERR) fprintf(stderr, "signal(SIGINT) error\n"); sigemptyset(&waitmask); sigaddset(&waitmask, SIGUSR1); sigemptyset(&newmask); sigaddset(&newmask, SIGINT); // 添加SIGINT信号,保存当前信号屏蔽字 if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) fprintf(stderr, "SIG_BLOCK error\n"); pr_mask("in critical region: "); // 等待除了SIGUSR1之外的信号 if (sigsuspend(&waitmask) != -1) fprintf(stderr, "sigsuspend erro\n"); pr_mask("after return from sigsuspend: "); // 恢复原来的信号屏蔽字 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) fprintf(stderr, "SIG_SETMASK error\n"); pr_mask("program exit: "); exit(0); } static void sig_int(int signo) { pr_mask("\nin sig_int: "); } // 输出信号屏蔽字 void pr_mask(const char *str) { sigset_t sigset; int errno_save; errno_save = errno; if (sigprocmask(0, NULL, &sigset) < 0) fprintf(stderr, "sigprocmask error\n"); printf("%s", str); if (sigismember(&sigset, SIGINT)) printf("SIGINT "); if (sigismember(&sigset, SIGQUIT)) printf("SIGQUIT "); if (sigismember(&sigset, SIGUSR1)) printf("SIGUSR1 "); if (sigismember(&sigset, SIGALRM)) printf("SIGALRM "); printf("\n"); errno = errno_save; }
注意,当sigsuspend返回时,它将信号屏蔽字设置为调用它之前的值。在本例中SIGINT信号被阻塞。因此将信号屏蔽字复位为早先保存的值(oldmask)。
$ ./15 program start: in critical region: SIGINT ^C 键入中断字符(Ctrl+C) in sig_int: SIGINT SIGUSR1 after return from sigsuspend: SIGINT program exit:
在调用sigsuspend时,将SIGUSR1信号加到了进程信号屏蔽字中(添加,确定?但,SIGINT信号没有被屏蔽。设置为),所以当运行该信号处理程序时,我们得知信号屏蔽字已经改变了。从中可见,在sigsuspend返回时,它将信号屏蔽字恢复为调用它之前的值。
sigsuspend的另一种应用是等待一个信号处理程序设置一个全局变量。程序清单10-16用于捕捉中断信号和退出信号,但是仅当捕捉到退出信号时,才唤醒主例程。
《UNIX环境高级编程》P271:程序清单10-16 用sigsuspend等待一个全局变量被设置
#include <stdio.h> #include <stdlib.h> #include <signal.h> volatile sig_atomic_t quitflag; static void sig_int(int signo) { if (signo == SIGINT) printf("\ninterrupt\n"); else if (signo == SIGQUIT) quitflag = 1; // 设置全局变量 } int main(void) { sigset_t newmask, oldmask, zeromask; if (signal(SIGINT, sig_int) == SIG_ERR) fprintf(stderr, "signal(SIGINT) error\n"); if (signal(SIGQUIT, sig_int) == SIG_ERR) fprintf(stderr, "signal(SIGQUIT) error\n"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); // 添加SIGQUIT,保存当前信号屏蔽字 if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) fprintf(stderr, "SIG_BLOCK error\n"); while (quitflag == 0) sigsuspend(&zeromask); // SIGQUIT被捕获, quitflag = 0; // 恢复信号屏蔽字 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) fprintf(stderr, "SIG_SETMASK error\n"); exit(0); }
此程序的示例输出:
$ ./16 ^C 键入中断字符(Ctrl+C) interrupt ^C interrupt ^C interrupt ^C interrupt ^\$ 用退出键终止(Ctrl+\)
可以用信号实现父、子进程之间的同步,这是信号应用的另一个实例。程序清单10-17包含了8.9节提到的五个例程的实现,它们是:TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT和WAIT_CHILD。
《UNIX环境高级编程》P272:程序清单10-17 父子进程可用来实现同步的例程(有改动)
#include <stdio.h> #include <stdlib.h> #include <signal.h> static volatile sig_atomic_t sigflag; static sigset_t newmask, oldmask, zeromask; static void sig_usr(int signo) { sigflag = 1; } void TELL_WAIT(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) fprintf(stderr, "signal(SIGUSR1) error\n"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) fprintf(stderr, "signal(SIGUSR2) error\n"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGUSR1); sigaddset(&newmask, SIGUSR2); // 添加 SIGUSR1 和 SIGUSR2,保存当前信号屏蔽字 if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) fprintf(stderr, "SIG_BLOCK error\n"); } void TELL_PARENT(pid_t pid) { kill(pid, SIGUSR2); // 通知父进程,准备完毕 } void WAIT_PARENT(void) { while (sigflag == 0) sigsuspend(&zeromask); // 等待父进程 sigflag = 0; // 恢复原来的信号屏蔽字 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) fprintf(stderr, "SIG_STEMASK error\n"); } void TELL_CHILD(pid_t pid) { kill(pid, SIGUSR1); // 通知子进程,准备完毕 } void WAIT_CHILD(void) { while (sigflag == 0) sigsuspend(&zeromask); // 等待子进程 sigflag = 0; // 恢复原来的信号屏蔽字 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) fprintf(stderr, "SIG_SETMASK error\n"); }
其中使用了两个用户定义的信号:SIGUSR1由父进程发送给子进程,SIGUSR2由子进程发送给父进程。
如果在等待信号发生时希望去休眠,则使用sigsuspend函数是非常适当的。但是如果在等待信号期间希望调用其他系统函数,可以使用多线程,专门安排一个线程处理信号。
如果不是用线程,那么我们能尽力做到做好的是,当信号发生时,在信号捕捉程序对一个全局变量置1。