普通版本mysleep和规避竞态条件的mysleep

      我们模拟实现一下sleep,称为mysleep,在实现之前先介绍三个函数alarm函数,sigaction函数,以及pause函数。

      首先我们来看alarm函数:

       #include <unistd.h>
       unsigned int alarm(unsigned int seconds);

       调用alarm函数可以设定一个闹钟,大家都知道,在现实生活中,闹钟的作用就是在你设定的时间后叫醒你,同理,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。这个函数的返回值是0(意味着闹钟准时响了)或者是以前设定的闹钟时间还余下的秒数(可能由于某些原因,闹钟提前响了)。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡 一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍是以前设定的闹钟时间还余下的秒数。如果余下的秒数为0,就是说操作系统在senconds秒之后准时的给当前进程发送了SIGALRM信号。如果余下的秒数大于0,说明操作系统由于某些原因在没有到senconds秒的时候给当前进程发送了SIGALRM信号,而返回值就是离senconds秒还差多少秒。

      其次,我们来看一个信号捕捉函数:

      #include <signal.h>
      int sigaction(int signo, const struct sigaction *act, structsigaction *oact);

      sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体。

      

     将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。

     接下来,解释一下结构体中的sa_mask是什么意思。当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
     sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,在这里不详细介绍这个字段了

     最后,我们来看一下,pause函数:

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

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

      在熟悉了这三种函数之后,我们开始编写mysleep,思路很简单。main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数myheadler,调用alarm(nsecs)设定闹钟。调用pause等待,内核切换到别的进程运行

nsecs秒之后,内核发SIGALRM给这个进程。然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理 动作。接下来看一下代码实现:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void  myhandler(int sig)
{}


int mysleep(int timeout)
{
    struct sigaction act,oact;
    act.sa_handler = myhandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGALRM,&act,NULL);

    alarm(timeout);
    pause();
    int ret = alarm(0);
    sigaction(SIGALRM,&oact,NULL);
    return ret;
}


int main()
{
    printf("hahahhhhah\n");
    mysleep(3);
    printf("wake up!\n");
    return 0;
}

       但是很遗憾,这个程序存在很大的一个bug,我设想一下这样的时序,注册SIGALRM信号的处理函数,调用alarm(nsecs)设定闹钟,内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间 nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函 数sig_alrm之后再次进入内核。返回这个进程的主控制流程,alarm(nsecs)返回,调用pause()挂起等待。可是SIGALRM信号已经处理完了。那么这个进程就会一直挂起等待了。
       出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优 先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)。

        我们想到的办法是,1.屏蔽SIGALRM信号,2.alarm(nsecs),3.解除对SIGALRM信号的屏蔽,4.pause(),但是在3和4之间也有一段时间,不保证这段时间信号不会被递达。如果3和4是同时执行的,是一个原子操作,那么这个问题就会很好的解决。

         sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调⽤用sigsuspend而不是pause。

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

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void handler(int sig)
{}


int mysleep(int timeout)
{
    struct sigaction act,oact;
    sigset_t newmask,oldmask,suspmask;

    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGALRM,&act,&oact);

    sigemptyset(&newmask);
    sigaddset(&newmask,SIGALRM);
    sigprocmask(SIG_BLOCK,&newmask,&oldmask);

    alarm(timeout);

    suspmask = oldmask;
    sigdelset(&suspmask,SIGALRM);
    
    sigsuspend(&suspmask);

    int ret = alarm(0);
    sigaction(SIGALRM,&oact,NULL);
    sigprocmask(SIG_SETMASK,&oldmask,NULL);
    return ret;
}

int main()
{
    printf("hello\n");
    mysleep(3);
    printf("wake up\n");
    return 0;
}







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值