在上一节中,我们使用了 sigpending 从未决队列中取出信号,并打印挨个打印,这种做法相当的麻烦,而且不太科学。如果没有未决信号集中一直没有信号,岂不是在浪费 cpu?
幸好,sigwait 函数可以帮我们解决这个问题。
1. sigwait 函数
如果在线程中调用 sigwait,它会一直等待它指定的信号,直到未决信号集中出现指定的信号为止,同时 sigwait 还会从未决信号集中取出该信号返回,并将该信号从未决非信号集中删除。
如果多线程中调用 sigwait 等待同一个信号,只会有一个线程可以从 sigwait 中返回。
如果 sigwait 要等待的信号被捕获,要么 sigwait 函数返回,要么调用相应的信号处理函数,到底是哪种情况这取决于操作系统实现。一般情况下,我们需要将 sigwait 要等待的信号添加到阻塞集中,以避免歧义。
sigwait 函数原型如下:
int sigwait(const sigset_t *set, int *sig);
参数 set 表示要等待哪些信号,一旦 sigwait 函数返回,会从未决信号集中取出信号,放到参数 sig 指向的内存中。
注意:在 apue 中文版本中,参数 sig 被翻译成了发送信号的数量……所以大家在看书的时候,对有疑惑的地方一定要看英文原版,然后做实验验证。
图1 翻译错误
图 1 中被圈出来的部分,翻译的都是有问题的,第一个刚刚已经指出了,另外一点,pending 这个单词不应该被翻译为挂起,而是未决。
虽然译者翻译错误,但是我们还是要感谢译者为此书的翻译倾注了大量心血,毕竟我们自己直接读原版是很费时费力的。最好的方式,就是两者结合起来,对照着看。
2. 实验
程序 sigwait.c 采用了另一种处理信号的方式。它将异步信号处理方式改成了同步处理(一个信号不会因为另一个信号被打断,信号的处理都是按照顺序一个一个来的),大大简化了程序的设计。
2.1 代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#define PPERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)
void* sig_thread(void *arg) {
sigset_t *st = arg;
int err, sig;
for(;;) {
// sigwait 函数一直等待指定的信号出现(在未决信号集中)
err = sigwait(st, &sig);
if (err != 0) PPERR(err, "sigwait");
// 在这里可以执行相应的处理程序对信号进行处理。
printf("handle sig: %d\n", sig);
}
return NULL;
}
int main() {
int err;
pthread_t tid;
sigset_t st;
sigemptyset(&st);
sigaddset(&st, SIGINT);
sigaddset(&st, SIGTSTP);
// 将我们想要处理的信号加入阻塞集,防止因为默认处理导致进程退出
err = pthread_sigmask(SIG_BLOCK, &st, NULL);
if (err != 0) PPERR(err, "pthread_sigmask");
pthread_create(&tid, NULL, sig_thread, (void*)&st);
pthread_join(tid, NULL);
return 0;
}
2.2 编译和运行
- 编译
$ gcc sigwait.c -o sigwait -lpthread
- 运行
图2 程序启动后,分别按下 CTRL+ C、CTRL + Z、CTRL + \
因为我们的程序没有处理 SIGQUIT 信号,所以按下 CTRL + \ 后,整个进程终止了。
3. 总结
- 掌握 sigwait 函数语义
- 理解文中的实验
思考:本文的信号处理方式和传统的注册信号处理函数有什么区别?不同的方式各有什么优缺点?
这里引用 apue 中的描述:
采用这种方式的好处在于防止信号打断线程。安排专用的线程处理函数,不需要担心信号处理程序中哪些函数是安全的,哪些是不安全的。因为这些函数调用都来自于同一个线程上下文,而非传统的会中断线程正常执行流程的信号处理程序。