竞态条件(race condition)是一个在设备或者系统试图同时执行两个操作的时候出现的不希望的状况,但是由于设备和系统的自然特性,为了正确地执行,操作必须按照合适顺序进行。
好比我们在模拟实现sleep()函数时
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int s)
{}
int mysleep(int sec)
{
signal(SIGALRM,handler);
alarm(sec);
pause();
int r=alarm(0);
return r;
}
int main()
{
mysleep(3);
printf("3 sec \n");
return 0;
}
这样实现基本可以完成sleep的功能,但在极端情况下,cpu负载很高,当程序刚刚执行完alarm(sec);
这行代码时,时间片切给了另外一个进程,等回来时,alarm的时间已经到了,这时直接处理SIGALRM信号,执行信号处理函数,这些都进行完毕,才来继续执行pause()
这句代码,因为信号已经在pause之前被处理过了,所以会一直阻塞在pause这里,而我们想要的是在pause()挂起时经过若干秒被SIGALRM信号打断然后pause返回,显然这与我们想要的结果相悖。
首先我们想到的是使用信号屏蔽来解决,在执行pause()之前一直屏蔽SIGALRM信号,等到即将执行pause时再解除屏蔽,但是这种做法似乎没效果,因为时间片也有可能在解除屏蔽与pause执行的间隙被切换出去,和上边做法没区别。。。
系统为我们提供了这么一个函数sigsuspend
int sigsuspend(const sigset_t* mask);
它相当于pause+信号屏蔽,是pause的加强版,可以用来解决竞态问题。
它的功能是在该函数执行期间用参数中传递 的信号屏蔽集覆盖原来的信号屏蔽集,函数执行完毕后又恢复为原来的信号屏蔽集。
小试一把先:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void handler(int s)
{
printf("recv %d\n",s);
sleep(3);
printf("finish\n");
}
int main()
{
signal(SIGINT,handler);
signal(SIGQUIT,handler);
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,NULL);
sigset_t pset;
sigemptyset(&pset);
sigaddset(&pset,SIGQUIT);
sigsuspend(&pset);
sigprocmask(SIG_UNBLOCK,&set,NULL);
}
这时我们使用这个函数来处理mysleep中的竞态问题,在执行sigsupend之前屏蔽SIGALRM信号,在sigsupend的参数中传一个空的信号屏蔽集,即取消SIGALRM的屏蔽,这样就能使SIGALRM信号一定能够在sigsupend期间被处理。
#include <stdio.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int s)
{}
int mysleep(int sec)
{
signal(SIGALRM,handler);
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGALRM);
sigprocmask(SIG_BLOCK,&set,NULL);
alarm(sec);
sigset_t pset;
sigemptyset(&pset);
sigsuspend(&pset);
sigprocmask(SIG_UNBLOCK,&pset,NULL);
int r=alarm(0);
return r;
}
int main()
{
mysleep(3);
printf("3 sec\n");
}