在前面的一篇文章中提到了关于信号的一些知识,可参考如下文章:
接下来在这篇文章中就谈一谈什仫是信号捕捉。
什仫是信号捕捉?
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
与信号捕捉有关的函数操作:
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;
}
在这里就分享结束了~~~