时序竟态问题和解决方法

通过一个mysleep程序来引入时序竟态产生的原因:
mysleep通过alarm函数定时,pause函数挂起的捆绑应用,来实现sleep函数的功能

  1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<errno.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 void catch_sigalarm(int signo)
  7 {
  8 
  9 }
 10 unsigned int mysleep(unsigned int sec)
 11 {
 12     int num;
 13     struct sigaction act,oldact;
 14 
 15     //由于pause函数只有对信号的处理方式为捕捉的情况下才会成功返回,所以要向内核注册一个捕捉函数cath_sigalarm;
 16     act.sa_handler = catch_sigalarm;//捕捉函数赋值
 17     sigemptyset(&act.sa_mask);//设置捕捉函数执行期间的mask(信号屏蔽字)。
 18     act.sa_flags = 0;//在捕捉函数执行期间,对该信息采取默认处理方式(自动屏蔽本信号)
 19     num = sigaction(SIGALRM,&act,&oldact); //注册对SIGALRM信号的捕捉函数
 20     if(num == -1)
 21     {
 22         perror("sigaction error");
 23         exit(1);
 24     }   
 25     
 26     alarm(sec);//定时sec秒后,向该进程发送SIGALRM信号。
 27     num = pause();//挂起等待信号
 28     //pause成功返回-1,且将errno置为EINTE
 29     if(num == -1 && errno == EINTR)//验证pause成功返回的方式
 30     {
 31         printf("pause success\n");
 32     }   
 33     
 34     alarm(0);//如果pause函数在alarm计时期间因为其他信号而被唤醒,则需要对alarm进行清零操作,避免它到时再发送信号。
 35     sigaction(SIGALRM,&oldact,NULL); //恢复SIGALRM信号默认的处理方式
 36     
 37     return num;
 38 }
 39 
 40 int main(void)
 41 {
 42     while(1){
 43         printf("------------\n");
 44         mysleep(5);
 45     }
 46     return 0;
 47 }

如果进程在alarm函数定时完后,由于时间片轮完失去CPU,也就是在26行到27行之间失去了cpu的使用权。如果在进程失去cpu这段时间里alarm计时完成发送信号,当进程再次占有cpu时,此时信号已抵达,内核会帮助进程处理该信号,处理完后程序继续执行到27行,此时执行pause函数将进程挂起,但是由于alarm早已将信号发出,在进程挂起后并收不到信号,所以进程将一直挂起。

虽然上述例子比较极端,发生概率极低,但是如果你的程序是服务类程序,运行在电脑上之后不会停止,在运行数千万次中只要出现一次这种情况就是致命的,所以我们在写程序是应当考虑该情况并避免。

我们如何避免这种情况呢?我们已经知道该问题出现的原因是因为在pause函数将进程挂起前信号就已经抵达并且被处理,然后才调用pause函数,导致pause无法收到信号被唤醒,所以我们只要使信号抵达后在被内核处理的同时执行pause函数就行了,这样内核调用捕捉函数后,pause可以成功返回。

总结起来就是使处理信号和挂起进程成为一次原子操作

为了解决上述问题,linux为我们专门提供了系统函数 sigsuspend(),其函数原型为int sigsuspend(const sigset_t *mask); mask为在函数sigsuspend执行期间设置的信号屏蔽字。

下面通过代码来具体说明如何利用sigsuspend函数来避免时序竟态问题。

  1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<errno.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 void catch_sigalarm(int signo)
  7 {
  8 
  9 }
 10 unsigned int mysleep(unsigned int sec)
 11 {
 12     int num;
 13     sigset_t newset,oldset,susset;
 14     struct sigaction act,oldact;
 15     
 16     //为SIGALRM设置捕捉函数
 17     act.sa_handler = catch_sigalarm;
 18     sigemptyset(&act.sa_mask);
 19     act.sa_flags = 0;
 20     num = sigaction(SIGALRM,&act,&oldact);
 21     if(num == -1)
 22     {
 23         perror("sigaction error");
 24         exit(1);
 25     }   
 26     
 27     //设置信号屏蔽字将SIGALRM屏蔽
 28     sigemptyset(&newset);
 29     sigaddset(&newset,SIGALRM);
 30     sigprocmask(SIG_BLOCK,&newset,&oldset);//将SIGALRM信号屏蔽
 31     
 32     //设置susset解除SIGALRM的屏蔽
 33     susset = oldset;
 34     sigdelset(&susset,SIGALRM);//去掉对SIGALRM的屏蔽
 35     
 36     alarm(sec);//定时sec秒后产生SIGALRM信号
 37     //在sigsuspend函数调用之前进程对SIGALRM信号屏蔽
 38     sigsuspend(&susset);//在sigsuspend调用后由于设置了susset没有对SIGALRM进行屏蔽,则信号抵达
 39     //这个时候就实现了进程挂起且信号抵达,因为sigsuspend函数调用后进程就挂起了,又由于设置了susset对SIGALRM解除了屏蔽,信号抵达。
 40     
 41     alarm(0);
 42     sigaction(SIGALRM,&oldact,NULL); //恢复SIGALRM信号默认的处理方式
 43     
 44     sigprocmask(SIG_SETMASK,&oldset,NULL);//恢复信号屏蔽字
 45     return num;
 46 }   
 47 
 48 int main(void)
 49 {
 50     while(1){
 51         printf("------------\n");
 52         mysleep(5);
 53     }
 54     return 0;
 55 }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值