Linux----普通版本的mysleep与规避竞态条件的mysleep对比

一、普通情况下的mysleep

       先了解一下SIGALRM信号、alarm函数和pause函数。

       SIGALRM时钟定时信号, 计算的是实际的时间或时钟时间。alarm函数使用该信号。

unsigned int alarm(unsigned int seconds);

       alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程。

#include <unistd.h>
int pause(void);

       pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终止, pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态, pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回- 1 , errno设置为EINTR,所以pause只有出错的返回值(想想以前还学过什么函数只有出错回值?)。错误码EINTR表示“被信号中断”。


   

      下面使用pause和alarm实现sleep(3)函数,称为mysleep:

       普通版本的mysleep代码实现:

运行结果:

    1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
    2. 调用alarm(seconds)设定闹钟。
    3. 调用pause等待,内核切换到别的进程运行。
    4. seconds秒之后,闹钟超时,内核发SIGALRM给这个进程。
    5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。
    6. 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽,从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
    7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。
需要注意的是虽然sig_alrm函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。此外,mysleep函数的返回值表示“未睡到”的时间,即unslept,当尚未计时到seconds而pause函数先被其他信号处理函数所中断返回,在外界看来就是在sleep期间被其他信号处理函数中断了,则mysleep返回非0值,即unslept。如sleep(3)的man
    手册写的返回值:
    RETURN VALUE: Zero if the requested time has elapsed, or the number of seconds left to sleep, if the call was interrupted by a signal handler.
    当然如果是被SIGALRM handler所中断,则表示睡眠时间到,mysleep返回值为0。


二、规避竞态条件下的mysleep

       1、 现在重新审视上面的mysleep函数,设想这样的时序:
       1). 注册SIGALRM信号的处理函数。
       2). 调用alarm(seconds)设定闹钟。
       3). 内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间
       4). seconds秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。
       5). 优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函数sig_alrm之后再次进入内核。
       6). 返回这个进程的主控制流程,alarm(seconds)返回,调用pause()挂起等待。
       7). 可是SIGALRM信号已经处理完了,还等待什么呢?
           出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(seconds)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(seconds)之后的seconds秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件(RaceCondition)。

       2、如何解决上述问题呢?我们可能会想到,在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。看看以下方法可行吗?
       1). 屏蔽SIGALRM信号;
       2). alarm(seconds);
       3). 解除对SIGALRM信号的屏蔽;
       4). pause();
       3、从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这个间隙,我们把解除屏蔽移到pause后面可以吗?
       1). 屏蔽SIGALRM信号;
       2). alarm(seconds);
       3). pause();
       4). 解除对SIGALRM信号的屏蔽;

        这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
        和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

        规避竞态条件下的mysleep的代码实现:

运行结果:

     如果在调用mysleep函数时SIGALRM信号没有屏蔽:
     1. 调用sigprocmask(SIG_BLOCK, &newmask, &oldmask);时屏蔽SIGALRM。
     2. 调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。
     3. SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
     4. 调用sigprocmask(SIG_SETMASK, &oldmask, NULL);时再次解除对SIGALRM的屏蔽


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值