在linux中,可以用一个称为信号集的数据类型 sigset_t,来表示所有的被阻塞信号的一个集合。对这个集合的操作函数有:
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum);
sigemptyset(&set);
*set = 0;
sigaddset 函数用来向信号集中添加一个信号,而 sigdelset 用来从给定的信号集中删除一个信号。sigismember 用来测试一个给定的信号是否存在于给定的信号集中。
sigprocmask 函数用来更改进程的信号屏蔽字,它的函数原型如下:
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
SIG_BLOCK:用来将 set 指定的信号屏蔽字和现在的信号屏蔽字按位或后,得到的结果作为新的信号屏蔽字 SIG_UNBLOCK:将 set 指定的信号屏蔽字从现在的信号屏蔽字中去除之后,得到的结果作为新的信号屏蔽字,这种情况与第一种情况正好相反 SIG_SETMASK:直接将 set 指定的信号屏蔽字作为新的信号屏蔽字
另外,这个函数中,如果 set 参数 NULL 的话,那么将不修改现在的信号屏蔽字,并且可以将现在的信号屏蔽字保存在 oldmask 中。我们也可以只设置信号屏蔽字,而不保存老的信号屏蔽字,只需要将 oldset 设置为NULL即可。
sigpending 函数用来返回对于当前进程阻塞或者是当前未决的信号集,它的函数原型如下:
#include <signal.h> int sigpending(sigset_t *set);
#include <signal.h> #include <stdio.h> #include <stdlib.h> static void sig_quit(int); int main(void) { sigset_t oldmask,newmask,pendmask; if(signal(SIGQUIT,sig_quit)<0) { printf("signal(SIGQUIT) error\n"); return -1; } sigemptyset(&newmask); sigaddset(&newmask,SIGQUIT); if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) { printf("SIG_BLOCK error\n"); return -1; } sleep(5); if(sigpending(&pendmask)<0) { printf("sigpending error\n"); return -1; } if(sigismember(&pendmask,SIGQUIT)) { printf("\nSIGQUIT pending\n"); } if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0) { printf("SET_MASK error\n"); return -1; } printf("SIGQUIT unblocked\n"); sleep(5); return 0; } static void sig_quit(int signo) { printf("caught SIGQUIT\n"); if(signal(SIGQUIT,SIG_DFL)==SIG_ERR) { printf("SIGQUIT error\n"); exit(-1); } }
./a.out
^\
SIGQUIT pending caught SIGQUIT SIGQUIT unblocked
sigaction 函数和 signal 函数十分的相似,它也是用来给信号注册处理函数的,它的函数原型如下:
#include <signal.h> int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction { void (*sa_handler)(int); sigset_t sa_mask; int sa_flags; };
#include <signal.h> #include <stdio.h> #include <stdlib.h> typedef void (Sigfunc) (int); Sigfunc* signal1(int signo,Sigfunc* func) { struct sigaction act,oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(signo==SIGALRM) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif } if(sigaction(signo,&act,&oact)<0) { return SIG_ERR; } return oact.sa_handler; }
上面的对 sa_flags 的设置说明,如果是 SIGALRM 信号的话,被打断的系统调用就不需要再重新启动,而如果是其他信号打断的系统调用就需要重新启动。
这两个函数和 setjmp 及 longjmp 是什么关系呢? sigsetjmp 和 siglongjmp 函数是用在信号处理程序中的。当调用 sigsetjmp 时,它会将当前的信号屏蔽字保存起来,当调用 siglongjmp 返回时,会恢复之前保存的信号屏蔽字。而 setjmp 和longjmp 函数却没有说明有这个功能,因此,系统向我们提供了这两个函数,这两个函数的函数原型是:
int sigsetjmp(sigjmp_buf env, int savesigs); void siglongjmp(sigjmp_buf env, int val);
当 sigsetjmp 中的第二个参数 savesigs 不为 0 ,就会将当前的信号屏蔽字保存在第一个参数 sigjmp 中。下面可以通过一个程序来演示一下这两个函数的使用,以及在执行信号处理程序时,系统的信号屏蔽字是如何包括刚刚被捕捉到的信号的。
#include <signal.h> #include <setjmp.h> #include <time.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> static void sig_usr1(int),sig_alrm(int); static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjmp; void pr_mask(const char* str); int main(void) { if(signal(SIGUSR1,sig_usr1)==SIG_ERR) { printf("signal(SIGUSR1) error\n"); return -1; } if(signal(SIGALRM,sig_alrm)==SIG_ERR) { printf("signal(AIGALRM) error\n"); return -1; } pr_mask("starting main..."); if(sigsetjmp(jmpbuf,1)) { pr_mask("ending main..."); exit(0); } canjmp = 1; for(;;) { pause(); } return 0; } void pr_mask(const char* str) { sigset_t sigset; int errno_save; errno_save = errno; if(sigprocmask(0,NULL,&sigset)<0) { printf("sigprocmask error\n"); exit(-1); } printf("%s\n",str); if(sigismember(&sigset,SIGINT)) printf("SIGINT\n"); if(sigismember(&sigset,SIGQUIT)) printf("SIGQUIT\n"); if(sigismember(&sigset,SIGUSR1)) printf("SIGUSR1\n"); if(sigismember(&sigset,SIGALRM)) printf("SIGALRM\n"); printf("\n"); errno = errno_save; } static void sig_usr1(int signo) { long starttime; if(canjmp == 0) { return; } pr_mask("in sig_usr1..."); alarm(3); starttime = time(NULL); for(;;) { if(time(NULL)>starttime+5) { break; } } pr_mask("ending sig_usr1..."); siglongjmp(jmpbuf,1); } static void sig_alrm(int signo) { pr_mask("in sig_alrm"); }将程序编译成 a.out,用下面命令在后台运行程序:
./a.out &
[1] 2928
kill -SIGUSR1 2928
in sig_usr1... SIGUSR1 asus@asus-K43SJ:~/unix/chapter10$ in sig_alrm SIGUSR1 SIGALRM ending sig_usr1... SIGUSR1 ending main... [1]+ Done ./a.out
sigsuspend 函数用来将设置信号屏蔽字和 pause 调用结合为一个原子操作,例如下面的代码:
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0) { exit(-1); } pause();在 sigprocmask 函数和 pause 函数之间发生的信号就会丢失,正是出于这个原因,我们提出了 sigsuspend 函数,这个函数的函数原型如下:
#include <signal.h> int sigsuspend(const sigset_t *mask);
#include <signal.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> static void sig_int(int); void pr_mask(const char* str); int main(void) { sigset_t oldmask,newmask,zeromask; if(signal(SIGINT,sig_int)==SIG_ERR) { printf("signal(SIGINT) error\n"); return -1; } sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask,SIGINT); if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) { printf("SIG_BLOCK error\n"); return -1; } pr_mask("in critical region..."); if(sigsuspend(&zeromask)!=-1) { printf("sigsuspend error\n"); return -1; } pr_mask("after return from sigsuspend..."); if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0) { printf("SIG_SETMASK error\n"); return -1; } pr_mask("ending main..."); return 0; } static void sig_int(int signo) { pr_mask("in sig_int..."); } void pr_mask(const char* str) { sigset_t sigset; int errno_save; errno_save = errno; if(sigprocmask(0,NULL,&sigset)<0) { printf("sigprocmask error\n"); exit(-1); } printf("%s\n",str); if(sigismember(&sigset,SIGINT)) printf("SIGINT\n"); if(sigismember(&sigset,SIGQUIT)) printf("SIGQUIT\n"); if(sigismember(&sigset,SIGUSR1)) printf("SIGUSR1\n"); if(sigismember(&sigset,SIGALRM)) printf("SIGALRM\n"); printf("\n"); errno = errno_save; }将程序编译成 a.out,运行程序,输出结果如下:
in critical region... SIGINT
^Cin sig_int... SIGINT after return from sigsuspend... SIGINT ending main...
下面我们再看一个,用 sigsuspend 函数用来同步父子进程的例子:
#include <stdio.h> #include <signal.h> #include <stdlib.h> #include <sys/types.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) { 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); if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) { printf("SIG_BLOCK error\n"); exit(-1); } } void TELL_PARENT(pid_t pid) { kill(pid,SIGUSR2); } void WAIT_PARENT(void) { while(sigflag==0) { sigsuspend(&zeromask); } sigflag = 0; } void TELL_CHILD(pid_t pid) { kill(pid,SIGUSR1); } void WAIT_CHILD(void) { while(sigflag==0) { sigsuspend(&zeromask); } sigflag = 0; } void charatatime(char* str) { char* ptr; char c; setbuf(stdout,NULL); for(ptr=str;*ptr!='\0';ptr++) { c = *ptr; putc(c,stdout); } } int main(void) { pid_t pid; TELL_WAIT(); if((pid=fork())<0) { printf("fork error\n"); exit(-1); } else if(pid==0) { WAIT_PARENT(); charatatime("output from child\n"); } else { charatatime("output from parent\n"); TELL_CHILD(pid); } return 0; }
system 函数用来在一个程序中调用另一个可执行程序,在 system 函数的实现中,需要在父进程中忽略 SIGINT 和 SIGQUIT 信号,并且阻塞 SIGCHLD 信号。这个函数的实现如下:
int system(const char* cmdstring) { int status; pid_t pid; struct sigaction ignore,saveintr,savequit; sigset_t chldmask,savemask; if(cmdstring==NULL) { return 1; } ignore.sa_handler = SIG_IGN; sigemptyset(&ignore.sa_mask); ignore.sa_flags = 0; if(sigaction(SIGINT,&ignore,&saveintr)<0) { printf("sigaction error\n"); return -1; } if(sigaction(SIGQUIT,&ignore,&savequit)<0) { printf("sigaction error\n"); return -1; } sigemptyset(&chldmask); sigaddset(&chldmask,SIGCHLD); if(sigprocmask(SIG_BLOCK,&chldmask,&savemask)<0) { printf("SIG_BLOCK error\n"); return -1; } if( (pid=fork())<0 ) { printf("fork error\n"); status = -1; } else if(pid==0) { sigaction(SIGQUIT,&savequit,NULL); sigaction(SIGINT,&saveintr,NULL); if(sigprocmask(SIG_SETMASK,&savemask,NULL)<0) { printf("SIG_SETMASK error\n"); return -1; } execl("/bin/bash","bash","-c",cmdstring,(char*)0); _exit(0); } else { while(waitpid(pid,&status,0)<0) { if(errno!=EINTR) { status = -1; break; } } } sigaction(SIGQUIT,&savequit,NULL); sigaction(SIGINT,&saveintr,NULL); if(sigprocmask(SIG_SETMASK,&savemask,NULL)<0) { printf("SIG_SETMASK error\n"); return -1; } return status; }