学科分类
目录
Linux编程

时序竞态

假设你打算休息,于是设置了一个时长为30分的闹钟,希望30分钟后闹钟将自己唤醒,再去进行某项工作;然而20分钟之后你收到通知,这项工作需要现在着手准备,于是你不得不先花费30分钟去完成了这项工作,工作完成后,你打算继续睡觉,但在20分钟前闹钟已经启动,此时再次进入睡眠后,闹钟不会再将你唤醒。对人类来说,重新设置闹钟,或者放弃休息进行别的活动,不过是瞬息就能做好的决定,但对计算机来说,突发事件导致的中断,可能会造成不恰当的执行顺序,从而导致程序异常。这种因设备或系统出现不恰当的执行时序,而得到不正确结果的现象称为时序竞态。

在“多学一招”中实现的mysleep()函数,大体实现了sleep()的功能,但是对其进一步分析,无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这两步之间失去cpu:屏蔽应在alarm()之后解除是毫无疑问的,然而在解除屏蔽的同时,进程若失去cpu,此时信号可以被处理,但进程的挂起状态不会结束。除非将解除屏蔽与唤醒操作合并为一个“原子操作”,否则这个问题无法解决。

Linux系统中提供了一个具备以上功能的函数——sigsuspend()。

sigsuspend()函数位于函数库signal.h中,其功能为更改进程的信号屏蔽字,并等待信号递达。sigsuspend()的函数声明如下:

int sigsuspend(const sigset_t *mask);

sigsuspend()的参数mask是一个传入参数,该参数将会暂时替换进程中原有的信号屏蔽字;sigsuspend()函数总是返回-1,并设置errno为EINTR。改良过的mysleep()函数及其使用方法如下案例所示。

案例8:使用alarm()和sigsuspend()自实现mysleep()函数。

 1    #include <stdio.h>
 2    #include <signal.h>
 3    #include <stdio.h>
 4    void sig_alrm(int signo)
 5    {
 6        //do something...
 7    }
 8    unsigned int mysleep(unsigned int seconds)
 9    {
 10        struct sigaction newact,oldact;
 11        sigset_t newmask,oldmask,suspmask;
 12        unsigned int unslept;
 13        //①为SIGALRM设置捕捉函数
 14        newact.sa_handler=sig_alrm;
 15        sigemptyset(&newact.sa_mask);
 16        newact.sa_flags=0;
 17        sigaction(SIGALRM,&newact,&oldact);
 18        //②设置阻塞信号集,屏蔽SIGALRM信号
 19        sigemptyset(&newmask);
 20        sigaddset(&newmask,SIGALRM);
 21        sigprocmask(SIG_BLOCK,&newmask,&oldmask);
 22        //③设置计时器
 23        alarm(seconds);
 24        //④构造临时阻塞信号集
 25        suspmask=oldmask;
 26        sigdelset(&suspmask,SIGALRM);
 27        //⑤采用临时阻塞信号集suspmask替换原有阻塞信号集(不包含SIGALRM信号)
 28        sigsuspend(&suspmask);            //挂起进程,等待信号递达
 29        unslept=alarm(0);
 30        //⑥恢复SIGALRM原有的处理动作,呼应注释①
 31        sigaction(SIGALRM,&oldact,NULL);
 32        //⑦解除对SIGALRM的屏蔽,呼应注释②
 33        sigprocmask(SIG_SETMASK,&oldmask,NULL);
 34        return unslept;
 35    }
 36    int main()
 37    {
 38        while(1){
 39            mysleep(2);
 40            printf("two seconds passed\n");
 41        }
 42        return 0;
 43    }

编译案例,执行程序,执行结果如下:

two seconds passed
two seconds passed
^C

系统中运行的进程数量与cpu的使用情况是难以掌控的,代码中虽然存在时序竞态问题,但这个问题并不是每次程序运行都会出现,并且这种问题无法利用gcc等调试工具捕捉,因此在编程时应考虑周全,并在对执行时态要求较高的地方使用sigsuspend()函数,防止此类问题的产生。

点击此处
隐藏目录