Linux提供阻塞信号的隐式和显式机制。
对于隐式机制,内核默认阻塞任何当前处理程序正在处理信号类型的待处理信号。
对于显式机制,程序可以通过sigprocmask函数与它的辅助函数,明确地阻塞与解除阻塞选定的信号。
#include
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signum);
int sigdelset(sigset_t *set,int signum); //成功返回0,失败返回-1
int sigismember(const sigset_t *set,int signum); //signum是set成员返回1,不是返回0,出错返回-1
sigemptyset初始化set为空集合,sigfillset把每个信号都添加到set中,sigaddset把signum添加到set,sigdelset从set集合中删除signum。
下面代码展示如何利用sigprocmask来临时阻塞接受SIGINT信号:
sigset_t mask,prev_mask;
sigemptyset(&mask);
sigaddset(&mask,SIGINT);
sigprocmask(SIG_BLOCK,&mask,&prev_mask);
sigprocmask(SIG_SETMASK,&prev_mask,NULL);
在编写信号处理程序时,要尽可能使用异步安全函数,像printf()与sprintf()函数是不安全的,write()函数是安全的。
可以使用SIO包中的安全函数。
在不用exit()函数退出的信号处理程序中,应该保存error的状态,并在函数结束时恢复该状态。
建议读者自行搜索并了解volatile和sig_atomic_t。
另外未处理的信号是不会排队的。因为在pending位向量中对于每一个信号只有两种状态,当第一个信号被接受后,第二个信号就只会被简单的丢弃。
核心思想是:如果存在一个未处理的信号,那么表示至少有一个信号已经到达了。
我们来看下面一个程序:
void handler1(int sig){
int olderrno=errno;
if((waitpid(-1,NULL,0))<0){
sio_error("waitpid error");
sio_puts("Handler reaped child\n");
sleep(1);
errno=olderrno;
}
int main(){
int i,n;
char buf[MAXBUF];
if(signal(SIGCHLD,handler1)==SIG_ERR)
unix_error("signal error");
for(i=0;i<3;i++){
if(Fork()==0){
printf("Hello from child %d\n",(int)getpid());
exit(0);
}
}
if((n=read(STDIN_FILENO,buf,sizeof(buf)))<0)
unix_error("read");
printf("Parent processing input\n");
while(1);
exit(0);
}
这里其实存在一个问题,三个子进程只会有两个被回收。因为第一个信号发送后立刻被接收,第二个信号存在待处理,第三个信号会被直接抛弃。
这里可以在handler中使用while去改进:
void handler2(int sig){
int olderrno=errno;
while(waitpid(-1,NULL,0)>0) Sio_puts("Handler reaped child\n");
if(errno!=ECHLD) Sio_error("waitpid error");
Sleep(1);
errno=olderrno;
}