计时赛什么是计时赛?执行同一个程序两次。一般情况下,两次执行后得到的结果应该是一样的。但是由于系统资源的竞争,两次执行后可能会得到不同的结果。这种现象就是计时赛跑。暂停函数函数原型:intpause(void);function功能:当进程调用pause函数时,会导致进程主动挂起(处于阻塞状态,主动让出CPU),等待信号将其唤醒。返回值:我们知道处理信号的方式有3种:1.默认动作;2.忽略处理;3.捕捉。进程收到信号后,会先处理响应信号,然后唤醒暂停函数。所以有以下几种情况:①如果信号默认的处理动作是终止进程,则进程会终止,也就是说进程一收到信号就会终止,暂停函数有根本没有机会回来;②如果默认处理信号的动作是忽略,进程会直接忽略信号,相当于没有收到信号,进程会继续处于挂起状态,pause函数不会返回;③如果信号的处理动作是捕获,进程调用信号处理函数后,pause返回-1,errno设置为EINTR,表示“被信号中断”。④暂停接收到的信号无法屏蔽。如果被屏蔽了,pause是无法被唤醒的。因为alarm函数可以在设定的时间后发送SIGALRM信号,而pause函数可以暂停进程等待信号,那么可以结合两者写一个sleep函数,如下:1#include2#include3#include45voidsig_alrm(intsigno)6{7/*无事可做*/8}910unsignedintmysleep(unsignedintnsecs)11{12unsignedintunslept;1314signal(SIGALRM,&sig_alrm);15unslept=alarm(nsecs);16pause();1718returnunslept;19}202122intmain(void)23{24while(1){25mysleep(2);26printf("两秒过去\n");27}2829return0;30}计时赛前言在说计时赛的具体现象之前,我们先来看一个生活中常见的场景:我想小睡10分钟,于是定了10分钟的闹钟,希望闹钟能叫醒我10分钟后起来。正常情况:定好闹钟,小睡一下,10分钟后自己醒来;异常情况:定了闹钟,躺下睡了2分钟,被同学叫醒玩,玩了20分钟又重新睡了。但在播放过程中,闹钟已经响起,不会再自行唤醒。这个例子和后面要讲的计时赛很相似。TimingRace问题分析我们回过头来看看上面写的mysleep程序。这个函数可能有如下顺序:SIGALRM的默认动作是终止进程,所以我们需要捕获它,为SIGALRM注册一个信号处理函数;调用alarm(1)函数1秒;当alarm(1)的调用结束时,计时器启动计时器。此时进程失去CPU,进入就绪状态等待CPU(相当于被同学叫醒打球)。失去CPU的方式可能是内核调度了一个优先级更高的进程来替代当前进程,使得当前进程无法获得CPU;我们知道如果alarm函数采用自然计时方式,那么无论进程状态如何,定时器都会一直计时。因此,1秒后,闹钟时间到,内核向当前进程发送SIGALRM信号。高优先级进程还未执行,当前进程仍无法获得CPU,继续处于就绪状态,无法处理信号(处于挂起状态);高优先级进程被执行,当前进程获得CPU资源,内核调度回当前进程执行。SIGALRM信号由进程传递和处理;处理完信号后,回到当前主控进程,并调用pause()函数,挂断等待alarm函数发送的SIGALRM信号唤醒自己;但是实际的SIGALRM信号已经被处理,pause()函数永远不会等到。解决时序竞争问题通过上面的时序分析,我们可以看出,时序竞争的原因是进程失去CPU时,已经发送了SIGALRM信号。为了防止这种现象,我们可以先阻塞信号,“捕获”它,然后在阻塞解除时立即调用pause函数挂断等待。这样即使在调用alarm时CPU丢失了,也可以在进程重新获得CPU时再次“释放”捕捉到的SIGALRM信号,从而唤醒后续的pause函数。但是,在解除阻塞和pause等待挂起信号之间还是有可能丢掉CPU的,除非把这两个步骤做成“原子操作”。Linux系统提供的sigsuspend函数就有这个功能。因此,在时序要求严格的情况下,应该使用sigsuspend函数代替pause函数。函数原型:intsigsuspend(constsigset_t*mask);函数功能:暂停等待信号;函数参数:mask,传入参数,sigsuspend函数调用时,进程信号掩码字由参数mask指定。具体用法:可以将某个信号(如SIGALRM)从临时信号掩码字掩码中删除,即在调用sigsuspend函数时对该信号进行unmask,然后挂起等待该信号。但是此时我们已经改变了进程的信号掩码字,所以调用sigsuspend函数后,进程的信号掩码字应该恢复到原来的状态。1#include2#include3#include45voidsig_alrm(intsigno)6{7/*无事可做*/8}910unsignedintmysleep(unsignedintnsecs)11{12structsigactionnewact,oldact;13sigset_tnewmask,oldmask,suspmask;14unsignedintunslept;1516//1.为SIGALRM设置捕获函数,一个空函数17newact.sa_handler=sig_alrm;18sigemptyset(&newact.sa_mask);19newact.sa_flags=0;20sigaction(SIGALRM,&newact,&oldact);2122//2.设置阻塞信号集,阻塞SIGALRM信号23sigemptyset(&newmask);24sigaddset(&newmask,SIGALRM);25sigprocmask(SIG_BLOCK,&newmask,&oldmask);//信号掩码字mask2627//3。定时n秒,时间到28alarm(nsecs)后才能产生SIGALRM信号;2930/*4。构造一组临时有效的阻塞信号,用于调用sigsuspend,31*UnblockSIGALRM在临时阻塞信号集中*/32suspmask=oldmask;33sigdelset(&suspmask,SIGALRM);3435/*5.在sigsuspend的调用过程中,使用临时阻塞信号集suspmask替换原来的阻塞信号集36*这个信号集不包含SIGALRM信号,同时挂起等待。37*当sigsuspend被信号唤醒并返回时,恢复原样一些阻塞信号集*/38sigsuspend(&suspmask);3940未睡=警报(0);41//6。恢复SIGALRM原来的处理动作,与之前的注释相呼应142sigaction(SIGALRM,&oldact,NULL);4344//7.解除对SIGALRM的阻塞,与之前的注释相呼应245sigprocmask(SIG_SETMASK,&oldmask,NULL);4647return(unslept);48}4950intmain(void)51{52while(1){53mysleep(2);54printf("Twosecondspassed\n");55}5657return0;58}可重入函数/不可重入函数当一个在调用执行过程中,函数还没有被调用,由于一定的时机,函数又被重复调用,这种情况称为“重入”。如果从信号处理程序返回,则进程断点处的正常指令序列将继续执行。从resume到断点重新执行,函数依赖的环境没有改变,说明这个函数是可重入的,否则就是不可重入的。如果要使函数可重入,则函数中不能包含全局变量和静态变量,也不能使用malloc或free。更多精彩内容,关注公众号良旭Linux,在公众号回复1024免费领取5T技术资料,包括:Linux、C/C++、Python、树莓派、嵌入式、Java、人工智能等。公众号回复加群,邀你加入达人如云技术交流群。最后,最近有很多朋友找我要一份Linux学习路线图,所以我结合自己的经验,利用业余时间熬夜一个月,整理了一本电子书。无论你是面试还是自我提升,相信都会对你有所帮助!免费送给大家,只求大家给我点个赞!电子书|LinuxDevelopmentLearningRoadmap也希望有小伙伴可以加入我的行列,把这本电子书做得更加完美!获得?希望老铁们来个三连击,让更多人看到这篇文章。推荐阅读:干货|程序员和高级架构师免费发送工件的必备资源|支持搜索的资源网站