信号的捕捉

信号的捕捉在Linux下机制:如图


由此可知此机制下发生了四次的模式切换:用户态--->内核态、内核态--->用户态、用户态--->内核态、内核态--->用户态,从中也可以看出进程处理信号的时机是从内核态切回到用户态时候,若这时有信号可以递达则去执行其自定义处理动作,具体执行完成以后则在返回到用户态main函数继续上下文执行,若处理动作为退出则就直接退出。

注意:自定义捕捉函数和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。


信号捕捉函数:

#include <signal.h>
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);  //读取或修改与指定信号相关联的处理动作
参数:

signo:信号编号

act:若act指针非空,则根据act修改该信号的处理动作

oact:若oact指针非空,则通过oact传出该信号原来的处理动作

act和oact指向sigaction结构体:

struct sigaction
{
     void (*sa_handler)(int);  //函数指针指向自定义捕捉函数或赋值与默认或忽略动作
     sigset_t sa_mask;    //通过此信号集参数可屏蔽当前进程中别的信号
     sa_flags;           //默认为0
     void (*sa_sigaction)(int,siginfo_t*,void*); //实时信号函数指针
};
返回值:

调用成功则返回0,出错则返回- 1;
注意:

将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号;

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止;

如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字.

pause函数:

#include <unistd.h>
int pause(void);
说明:使当前进程挂起直至收到一个自定义捕捉信号出错返回;

若收到信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR, 所以pause只有出错的返回值。


运用以上函数与以前知识模拟实现sleep函数:

代码如下:

mysleep.c

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void myhandler(int sig)    
{
}
int mysleep(int seconds)
{
	struct sigaction act;
	struct sigaction oact;
	act.sa_handler=myhandler;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	sigaction(SIGALRM,&act,&oact);  //用此函数自定义信号SIGALRM捕捉函数 
 
	alarm(seconds);    //设定闹钟函数 
	pause();   //挂起操作直至收到自定义捕捉函数的信号 
	sigaction(SIGALRM,&oact,&act);   //恢复SIGALRM信号以前动作 
	int ret=alarm(0);   //取消闹钟 
	return ret;     //返回以前设定闹钟的剩余秒数 
}
int main()
{
	while(1)
	{
		int ret=mysleep(3);    //自定义实现sleep()函数 
		printf("mysleep done......%d\n",ret);
	}
}
运行结果如下:



在以上实现中虽然实现了sleep功能,但其有一个问题:

1.当在代码中调用alarm(seconds)时,这时并没有执行下一条指令pause()使进程挂起;

2.这时若有内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间;

3.当seconds秒之后闹钟超时了,内核发送SIGALRM信号给被取代切出的进程,其信号处于未决状态;

4.当优先级更高的进程执行完了,内核调度会使被取代切出进程执行,这时SIGALRM信号递达,执行其自定义捕捉函数之后再次进入内核;

5.最后返回这个进程的主控制流程,alarm(seconds)返回,再调用pause()挂起等待;

但这时由于别的进程优先级高影响了此进程调度时间,使alarm()函数seconds秒发送的SIGALRM信号已经处理完成,所以pause使进程挂起后将不会在收到自定义捕捉的信号。

竞态条件:以上alarm()紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm()之后的seconds秒之内被调用,是由于异步事件在任何时候都有可能发生(这里异步事件指出现更高优先级的进程),从而可能由于时序问题而导致错误,这叫做竞态条件.


解决以上问题:可以使在调用pause()之前一直使SIGALRM信号阻塞(即使当pause()调用alarm()之后的seconds秒之内没有被调用,但由于信号阻塞它也不会递达),直至在调用pause时取消其阻塞并使进程挂起(合成原子操作)

用以下函数实现:

int sigsuspend(const sigset_t *sigmask);//解除对某信号屏蔽并使当前进程挂起
参数:

sigmask:进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除当前进程中对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值(即原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的).

返回值:

与pause相同,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。

依此来实现sleep函数解除竞态条件问题的优化:

SafeMysleep.c

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

//实现解决时序问题引起的竞态条件问题的sleep()函数 
void myhandler(int sig)
{
}
int mysleep(int seconds)
{
	struct sigaction act;
	struct sigaction oact;
	act.sa_handler=myhandler;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	sigaction(SIGALRM,&act,&oact);   //定义信号SIGALRM的自定义捕捉函数 

	sigset_t newset,oldset,sigmask;
	sigemptyset(&newset);
	sigemptyset(&oldset);
	sigaddset(&newset,SIGALRM);
	sigprocmask(SIG_BLOCK,&newset,&oldset);   //阻塞信号SIGALRM 

	alarm(seconds);   //设定闹钟 

	sigmask=oldset;
	sigdelset(&sigmask,SIGALRM);
	sigsuspend(&sigmask);     //将信号SIGALRM取消阻塞并挂起当前进程 
                              //此函数调用完后SIGALRM恢复阻塞     
	sigaction(SIGALRM,&oact,&act);   //恢复SIGALRM信号以前动作 
	sigprocmask(SIG_SETMASK,&oldset,&newset);  //恢复当前系统旧的阻塞集 
	int ret=alarm(0);   //取消闹钟 
	return ret;
}
int main()
{
	while(1)
	{
		int ret=mysleep(3);
		printf("mysleep done......%d\n",ret);
	}
}
结果如下:






  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值