Linux-竞态条件-用信号机制实现sleep函数

相关接口函数

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

my_sleep

思路:

  • main函数调用my_sleep函数,后者调用sigaction函数注册了SIGALRM信号的处理函数sig_alarm。
  • 调用alarm(nsecs)设定闹钟。
  • 调用pause等待,内核切换别的进程等待。
  • nsecs秒之后,闹钟超时,内核发送SIGALRM信号给这个进程。
  • 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alarm;
  • 切换到用户态执行sig_alarm函数,进入sig_alarm函数时SIGALRM函数被屏蔽,从sig_alarm函数返回时SIGALRM信号自动解除屏蔽。然后再次进入内核态,再由内核态返回用户态继续执行主流程。
void sig_alarm(int signo)
{
    //Do Nothing
}
unsigned int my_sleep(unsigned int nsec)
{
    struct sigaction new_act;
    struct sigaction old_act;
    unsigned int unslept=0;
    new_act.sa_handler=sig_alarm;
    new_act.sa_flags=0;
    sigaction(SIGALRM,&new_act,&old_act);//注册信号处理函数
    alarm(nsec);//设定闹钟
    pause();///挂起进程
    unslept=alarm(0);//取消闹钟
    sigaction(SIGALRM,&old_act,NULL);//恢复默认信号处理处理动作
    return unslept;

}
int main()
{
    while(1)
    {
        my_sleep(5);
        printf("5 seconds!\n");
        break;
    }
    return 0;
}

几个问题:
1.为什么要注册信号处理函数:虽然自定义的sig_alarm的函数什么都没有做,但是必须要注册该信号处理函数,否则无法捕捉该SIGALRM信号。倘若不注册,那么该信号递达时会执行SIGALRM信号的默认执行动作-终止进程。所以进程不会被挂起。
2.为什么在函数返回前,要恢复该信号的默认处理动作:如果不回复,在该之后的代码中如果再次接收到该信号本应该执行该信号的默认处理动作是终止进程,但是现在由于被捕捉并且没有恢复就会出现不终止而是去调用注册的信号处理函数。

但是以上代码会有几个问题:
比如:在设定好闹钟之后,还未执行挂起进程语句,进程被切换去执行其他进程,但是当进程被切换回来后,这时有可能定时已过,闹钟已经响过了,在执行挂起进程,这时该进程将被永远挂起了。显示这是不合适的。
出现这个问题的原因是系统允许时的时序并不像我们写程序时所设想的一样。虽然alarm(nsecs)的下一句就是pause(),但是无法保证调用pause()就是在调用alarm之后的n秒内被调用。由于异步事件在任何时候都有可能发生,原因是出现优先级更高的进程时会被切换,所以这类由于时序问题引起的错误,称作竞态条件。

所以我们要完善一下思路:
在设定闹钟之前,我们先阻塞该SIGALRM信号,若是进程被切换,再次切换回来时,进程被挂起,但是该信号虽然提前响了,但也只是处于未决状态与阻塞状态,不会被递达。然后解除阻塞之后,该信号才会被立即递达。

  1. 屏蔽信号
  2. alarm(nsecs)
  3. 解除屏蔽
  4. pause()

但是还是有个问题:在解除屏蔽之后与挂起进程之间存在时间间隙,有可能会是在解除屏蔽之后到pause执行的这段时间内该信号被递达了。
所以我们需要考虑将解除屏蔽与挂起进程一步到位,合成原子操作才可以。所以需要用到下面这个函数:

int sigsuspend(const sigset_t *mask);
该函数在挂起进程的同时,也解决了时序问题。

void sig_alarm(int signo)
{
    //Do Nothing
}
unsigned int my_sleep(unsigned int nsec)
{
    struct sigaction new_act;
    struct sigaction old_act;
    sigset_t newmask,oldmask,suspmask;
    unsigned int unslept=0;
    new_act.sa_handler=sig_alarm;
    sigemptyset(&new_act.sa_mask);
    new_act.sa_flags=0;
    sigaction(SIGALRM,&new_act,&old_act);//注册信号处理函数
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGALRM);
    sigprocmask(SIG_BLOCK,&newmask,&oldmask);
    alarm(nsec);//设定闹钟
    suspmask=oldmask;
    sigdelset(&suspmask,SIGALRM);
    sigsuspend(&suspmask);
    unslept=alarm(0);//取消闹钟
    sigaction(SIGALRM,&old_act,NULL);//恢复默认信号处理处理动作
    sigprocmask(SIG_SETMASK,&oldmask,NULL);
    return unslept;

}
int main()
{
    while(1)
    {
        my_sleep(5);
        printf("5 seconds!\n");
        break;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值