在前面的一篇文章中提到了关于信号的一些知识,可参考如下文章:
Linux中关于信号的一些知识
接下来在这篇文章中就谈一谈什仫是信号捕捉。 什仫是信号捕捉? 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
与信号捕捉有关的函数操作: 1.读取/修改与指定信号相关联的处理动作,与signal函数类似;
#include <signal.h> int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact); #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);返回值:成功返回0,失败返回-1; signum:指定信号的编号; 若act指针非空,则根据act修改该信号的处理动作;若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };sa_handler:早期的捕捉函数; 赋值为常数SIG_IGN传给sigaction表示忽略信号;赋值为常数SIG_DFL表示执行系统默认动作;赋值为一个函数指针表示用户自定义函数捕捉信号,或者说向内核注册了一个信号处理函数; sa_sigaction:新添加的捕捉函数,通过sa_flags选择哪种捕捉函数; 新添加的捕捉函数,通过sa_flags选择哪种捕捉函数。 sa_mask:说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字; sa_flags:包含一些选项,一般都将其设置为0; sa_restorer:保留,已过期; 2.pause
#include <unistd.h> int pause(void);函数功能:pause函数使调用进程挂起直到有信号递达; pause可能有以下几种情况: 1).如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回; 2).如果信号的处理动作是忽略,则进程继续处于挂起态,pause不返回; 3).如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR, 所以pause只有出错的返回值。 这个函数就有点类似进程的程序替换了。
信号捕捉时候的状态转化: 从上面这张图就可以看出信号捕捉时的状态转化: 1).当你遇到中断,异常或者系统调用的时候由用户态进入内核态; 2).此时产生信号,由内核态切换到用户态,在这个过程中需要对pcb的那三张表进行检查,才会发现此时有递达的信号,此时就去去处理信号对应的操作,也就是信号处理函数; 3).处理信号处理函数的时候,这个时候为了安全的问题,这个时候为用户态; 4).信号处理函数结束后,然后从用户态切换到内核态。 5.然后由内核态切换到中断异常执行处的用户态。 所以通过以上的分析可知,在信号捕捉的状态转化过程中存在四次转化。
一个关于信号捕捉的简单的例子: 使用sigaction函数,pause函数以及alarm函数模拟sleep函数。
#include<stdio.h> #include<signal.h> #include<unistd.h> void my_handler(int signo) { //自定义的处理动作为空 } int mysleep(int timeout) { struct sigaction act,oact; act.sa_handler=my_handler; //用户自定义的信号处理动作 sigemptyset(&act.sa_mask); //清空该信号量 act.sa_flags=0; sigaction(SIGALRM,&act,&oact); //注册闹钟信号处理函数 alarm(timeout); //设置闹钟 pause(); //挂起 //有可能正常返回也有可能异常返回 int ret=alarm(0); sigaction(SIGALRM,&oact,NULL); //恢复信号之前的处理动作 return ret; } int main() { int count=0; while(1) { count++; mysleep(1); printf("mysleep %d\n",count); } // struct sigaction act,oact; // act.sa_handler=my_handler; // sigemptyset(&act.sa_mask); // act.sa_flags=0; // sigaction(2,&act,&oact); // while(1); return 0; }点击此处查看源码
这段代码有没有什仫问题呢?试想一下这样的情况:当我们执行完alarm之后在pause之前,别的进程会竞争夺走了CPU,夺走n秒后,SIGALRM递达了,然后n秒过后,这个时候就去执行pause,这样没有了SIGALRM这个信号,此时这个进程就无法在timeout时间内被唤醒就会一直被阻塞。 像这样由于时序问题而导致的错误,这就叫做竞态条件。 而解决这个问题我们就需要让alarm和pause是一个原子操作,该进程在alarm之后无法被别的进程夺走CPU的使用权。 Linux中存在这样一个函数sigsuspend就提供了这样的操作。
#include <signal.h> int sigsuspend(const sigset_t *mask);返回值:类似pause函数没有成功时的返回值。sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回为-1,errno设置为EINTR; mask:指定进程的信号屏蔽字,可以通过指定mask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来该信号是屏蔽的,从sigsuspend返回后依然是屏蔽的; 下面是我使用sigsuspend函数实现的mysleep函数:
#include<stdio.h> #include<signal.h> #include<unistd.h> void my_handler(int signo) { //自定义的处理动作为空 } //int mysleep(int timeout) //{ // struct sigaction act,oact; // act.sa_handler=my_handler; //用户自定义的信号处理动作 // sigemptyset(&act.sa_mask); //清空该信号量 // act.sa_flags=0; // sigaction(SIGALRM,&act,&oact); //注册闹钟信号处理函数 // alarm(timeout); //设置闹钟 // pause(); //挂起 // //有可能正常返回也有可能异常返回 // int ret=alarm(0); // sigaction(SIGALRM,&oact,NULL); //恢复信号之前的处理动作 // return ret; //} int mysleep(int timeout) { struct sigaction act,oact; act.sa_handler=my_handler; //用户自定义的信号处理动作 sigemptyset(&act.sa_mask); //清空该信号量 act.sa_flags=0; sigaction(SIGALRM,&act,&oact); //注册闹钟信号处理函数 //使得alarm和pause成为原子操作 sigset_t mask,omask,smask; sigemptyset(&mask); sigaddset(&mask,SIGALRM); sigprocmask(SIG_BLOCK,&mask,&omask); alarm(timeout); //设置闹钟 smask=omask; sigdelset(&smask,SIGALRM); sigsuspend(&smask); int ret=alarm(0); sigaction(SIGALRM,&oact,NULL); //恢复信号之前的处理动作 sigprocmask(SIG_SETMASK,&omask,NULL); return ret; } int main() { int count=0; while(1) { count++; mysleep(1); printf("mysleep %d\n",count); } // struct sigaction act,oact; // act.sa_handler=my_handler; // sigemptyset(&act.sa_mask); // act.sa_flags=0; // sigaction(2,&act,&oact); // while(1); return 0; }在这里就分享结束了~~~
