一个能表示多个信号的数据类型。
#include <signal.h> int sigemptyset(sigset_t *set); //初始化由set指向的信号集,清除其中所有信号 int sigfillset(sigset_t *set); //初始化由set指向的信号集,使其包括所有信号。 int sigaddset(sigset_t *set, int signum); //将一个信号添加到已有的信号集 int sigdelset(sigset_t *set, int signum); //从信号集删除一个信号 //4个函数返回值:若成功,返回0,若出错,返回-1 int sigismember(const sigset_t *set, int signum); //测试signum是否在set信号集中 //返回值:若真,返回0,若假,返回0调用sigprocmask可以检测或更改或同时进行检测和更改进程的信号屏蔽字。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); //返回值:若成功,返回0,若出错,返回-1 oldset是非空指针,那么进程的当前信号屏蔽字通过oldset返回。set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。sigprocmask函数仅为单线程进程定义。 how说明SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=maskSIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~setSIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask=setsigpending函数返回一个信号集,对于调用进程而言,其中的各信号是阻塞不能传递的,因而也一定是当前未决的。
#include <signal.h> int sigpending(sigset_t *set); //返回值:若成功,返回0,若出错,返回-1首先构造一个信号屏蔽字,然后添加要阻塞的信号,读出当前进程的未决信号集,调用sigprocmask函数更改进程的信号屏蔽字 运行结果:
➜ SIGNAL ./sigset 64bit sigset_t类型的大小为:128 0000000000000000000000000000000 0000000000000000000000000000000 //未决信号集为空。 ^C0100000000000000000000000000000 //Ctrl + C 发送SIGINT信号,SIGINT信号被阻塞,并打印未决信号集,下标为1的位被置1 0100000000000000000000000000000 0100000000000000000000000000000 ^\0110000000000000000000000000000 //Ctrl + \ 发送SIGQUIT信号,SIGQUIT信号被阻塞,并打印未决信号集,下标为2的位被置1 0110000000000000000000000000000 0110000000000000000000000000000 0110000000000000000000000000000 0110000000000000000000000000000 0110000000000000000000000000000 //10s之后SIGQUIT信号被清除,SIGINT进程被阻塞,进程因SIGQUIT信号而退出函数sigaction的功能是检查、修改与指定信号相关联的处理动作
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //返回值:若成功,返回0,若出错,返回-1 参数signo是要检测或修改其具体动作的信号编号。oldact返回信号的上一个动作,act指定要修改的动作。sigaction的结构如下:
struct sigaction { void (*sa_handler)(int); //捕捉函数 void (*sa_sigaction)(int, siginfo_t *, void *); //新添加的捕捉函数,可以传参 , 和sa_handler互斥,两者通过sa_flags选择采用哪种捕捉函数 sigset_t sa_mask; //在执行捕捉函数时,设置阻塞其它信号,sa_mask | 进程阻塞信号集,退出捕捉函数后,还原回原有的 阻塞信号集 int sa_flags; //SA_SIGINFO 或者 0 };运行结果:
➜ SIGNAL ./sigaction ********** ^CI am do_sig //Ctrl + C 发送SIGINT信号 num = 2 ^C^C^Cnum = 2 //连续多次发送SIGINT信号 ^\num = 2 //Ctrl + \ 发送一次SIGQUIT信号 num = 2 num = 2 I am do_sig num = 2 num = 2 num = 2 num = 2 num = 2 [1] 1485 quit (core dumped) ./sigaction程序分析:
定义一个sigaction的变量,并且将信号捕捉函数的首地址赋值给sa_handler成员。初始化一个信号集,并且添加SIGQUIT信号,因此如果在执行捕捉函数时会阻塞SIGQUIT信号,并设置捕捉的信号为SIGINT。当程序运行起来,当按下Ctrl+C发送一个SIGINT信号时,系统从用户态进入内核态,当内核处理完异常 处理递送的信号时,需要进入用户态调用捕捉函数在调用捕捉函数的执行n–的5秒期间,又发送三次SIGINT信号和一次SIGQUIT信号,但是在执行捕捉函数的期间因为之前设置的信号集会阻塞这些信号。当执行完捕捉函数后,会执行特殊的系统调用sigreturn,再次陷入内核态,然后又从内核态返回上次被中断的地方(while(1)的死循环)继续执行,接着信号屏蔽字会恢复为原先值。因为信号屏蔽字的恢复,上次阻塞的信号再次引发中断,但当同一个信号多次发生,通常不会加入队列当被解除阻塞后,信号处理函数只被调用一次。因此,在执行完第一个I am do_sig后即使多次发送SIGINT但也只会在执行一次I am do_sig。当执行完第二次SIGINT后,又会以相同的方式处理SIGQUIT信号,因为信号集恢复为原先值,因此,进程被SIGQUIT信号终止。signal函数由ISO C定义,不最好使用sigaction代替该函数。
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); //返回值:若成功,返回以前的信号处理配置,若出错,返回SIG_ERR 第一个参数指定信号的值,第二个参数指定针对前面信号值的处理handler可以是SIG_IGN(忽略该信号)、SIG_DFL(系统默认动作)或者是接到此信号后要调用的函数地址。我们称这种处理为捕捉该信号,称此函数为信号处理程序或信号捕捉函数。如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR运行结果:
➜ SIGNAL ./signal ************** ************** ^CSIGINT //Ctrl + C 发送SIGINT信号 ************** ************** ^CSIGINT //Ctrl + C 发送SIGINT信号 ************** ^\[1] 12618 quit (core dumped) ./signal //发送SIGQUIT退出程序system函数执行一条shell命令
#include <stdlib.h> int system(const char *command);进程捕捉的信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时终端,它首先执行该信号处理程序中的指令,如果从信号处理程序返回,则继续执行在捕捉的信号时进程正在执行的正常指令序列。但是在返回控制时不会出现任何错误,就叫可重入函数。而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
例如:
strtok_r函数是strtok函数的可重入版本,并且线程安全。用来分解一个字符串为一组字符串。
#include <string.h> char *strtok(char *str, const char *delim); char *strtok_r(char *str, const char *delim, char **saveptr); str是被分割的字符串地址delim函数的为分隔符字符串地址saveptr是指向char *类型的指针变量,用来保存strtok_r内部保存切分时的上下文,以应对连续调用分解相同源字符串。 #include <stdio.h> #include <string.h> int main(void) { char str[] = "everthing will be OK!"; char *saveptr = str, *p; while((p = strtok_r(saveptr, " ", &saveptr)) != NULL) printf("%s\n", p); return 0; }运行结果:
➜ SIGNAL ./strtok_r everthing will be OK!可重入函数见man 7 signal
pause函数可以在逻辑上实现简易的sleep函数
1. signal(SIGALRM, do_sig); 2. alarm(n); 3. pause();先设置一个SIGALRM信号,当alarm定时了n秒之后,pause接收到SIGALRM信号,然后返回。 但是,这存在着BUG,因为形成了竟态条件(race condition),会涉及时序竟态的问题。
竞态条件(race condition),从多进程间通信的角度来讲,是指两个或多个进程对共享的数据进行读或写的操作时,最终的结果取决于这些进程的执行顺序。
当程序执行完第二步alarm函数,此时如果有优先级更高的进程抢占了CPU的使用权,此时该进程就会处于就绪状态,当该进程被拉起时,n秒已经过去,SIGALRM信号已经丢失,那么该进程则会一直处于pause状态。
为了使信号不丢失,就可是设置信号屏蔽字阻塞。
1. signal(SIGALRM, do_sig); 2. 阻塞SIGALRM 3. alarm(n); 4. 解除阻塞SIGALRM 5. pause();但是在第4和第5步中间,仍然会形成竟态条件。因此需要一个原子操作先恢复信号屏蔽字,然后使进程休眠。而sigsuspend函数提供此功能,因此,就有了sigsuspend函数。
#include <signal.h> int sigsuspend(const sigset_t *mask); //返回-1,并将errno设置为EINTRsigsuspend函数的行为如下(原子操作):
通过参数mask解除对某个信号的屏蔽挂起进程当被信号唤醒,sigsuspend返回,进程的信号屏蔽字恢复为原来的值。使用sigaction代替signal,使用sigsuspend代替pause
#include <stdio.h> #include <unistd.h> #include <signal.h> void sig_arm() { } unsigned int mysleep(unsigned int nsecs) { struct sigaction newact, oldact; sigset_t newmask, oldmask, suspmask; unsigned int unslept; //设置信号捕捉函数,保存之前信号动作 newact.sa_handler = sig_arm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); //阻塞SIGALRM信号,保存当前信号屏蔽字 sigemptyset(&newmask); sigaddset(&newmask, SIGALRM); sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(nsecs); suspmask = oldmask; sigdelset(&suspmask, SIGALRM); //参数suspmask设置为取消阻塞SIGALRM信号,保证sigsuspend一定不阻塞SIGALRM,并且休眠进程,同时等待信号发生 sigsuspend(&suspmask); //设置未睡眠够秒数,恢复之前的信号动作 unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); //进程恢复为原来的信号屏蔽字 sigprocmask(SIG_SETMASK, &oldmask, NULL); return unslept; } int main(void) { int n = 5; while(n--) { mysleep(1); printf("1 sec passed\n"); } }运行结果:
➜ SIGNAL ./mysleep1 1 sec passed 1 sec passed 1 sec passed 1 sec passed 1 sec passed运行结果:
child ID:25348 child ID:25347 child ID:25349 child 25340 exit 100 //子进程退出值为进程return 的 100 child 25345 exit 100 child 25346 exit 100 Parent ID:25339 child 25343 exit 100 child 25341 exit 100 Parent ID:25339 child 25344 exit 100 Parent ID:25339 child 25342 exit 100 Parent ID:25339 child 25347 exit 100 child 25348 exit 100 Parent ID:25339 child 25349 exit 100 Parent ID:25339 Parent ID:25339