当前位置: 首页 > 科技观察

Linux系统编程——计时赛

时间:2023-03-12 10:31:18 科技观察

##计时赛什么是计时赛?执行同一个程序两次。正常情况下,两次执行的结果应该是一样的。但是由于系统资源的竞争,两次执行后可能会得到不同的结果。这种现象就是计时赛跑。##暂停函数函数原型:intpause(void);function功能:当进程调用pause函数时,会导致进程主动挂起(处于阻塞状态,主动让出CPU),等待信号将其唤醒。返回值:我们知道处理信号的方式有3种:1.默认动作;2.忽略处理;3.捕捉。进程收到信号后,会先处理响应信号,然后唤醒暂停函数。所以有以下几种情况:如果信号默认的处理动作是终止进程,则进程会终止,也就是说进程一收到信号就会终止,pause函数没有回归的机会;如果信号的默认处理动作是Ignore,进程会直接忽略信号,相当于没有收到信号,进程继续处于挂起状态,pause函数不返回;如果信号处理动作为捕获,则进程调用信号处理函数后,pause返回-1,errno设置为EINTR,表示“被信号中断”。pause接收到的信号无法屏蔽。如果被屏蔽了,pause是无法被唤醒的。因为alarm函数可以在设定的时间后发送SIGALRM信号,而pause函数可以挂起进程等待信号,两者结合可以自己写一个sleep函数,如下:#include#include<信号。h>#includevoidsig_alrm(intsigno){/*nothingtodo*/}unsignedintmysleep(unsignedintnsecs){unsignedintunslept;signal(SIGALRM,&sig_alrm);unslept=alarm(nsecs);pause();returnunslept;}intmain(void){while(1){mysleep(2);printf("Twosecondspassed\n");}return0;}##TemporaryRace初步例子在说计时race的具体现象之前,我们先来看一个生活中常见的场景:我想小睡10分钟,于是我定了一个10分钟的闹钟,希望闹钟能在10分钟后叫醒我。正常情况:定好闹钟,小睡一下,10分钟后自己醒来;异常情况:定好闹钟,躺下睡2分钟,被同学叫醒玩,玩了20分钟再睡。但在播放过程中,闹钟已经响起,不会再自行唤醒。这个例子和后面要讲的计时赛很相似。##TimingRace问题分析我们回头看看上面写的mysleep程序。这个函数可能有如下顺序:SIGALRM默认的动作是终止进程,所以我们需要捕获它,为SIGALRM注册一个信号处理函数;调用alarm(1)函数1秒;alarm(1)的调用结束,timer开始计时。此时进程失去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)从临时信号掩码wordmask中删除,即调用sigsuspend函数时对该信号进行unmask,然后挂起wait信号。但是此时我们已经改变了进程的信号掩码字,所以调用sigsuspend函数后,进程的信号掩码字应该恢复到原来的状态。#include#include#includevoidsig_alrm(intsigno){/*nothingtodo*/}unsignedintmysleep(unsignedintnsecs){structsigactionnewact,oldact;sigset_tnewmask,oldmask,suspmask;unsignedintunslept;//1。为SIGALRM设置捕获函数,空函数newact.sa_handler=sig_alrm;sigemptyset(&newact.sa_mask);newact.sa_flags=0;sigaction(SIGALRM,&newact,&oldact);//2.设置阻塞信号集,阻塞信号SIGALRMsigemptyset(&newmask);sigaddset(&newmask,SIGALRM);sigprocmask(SIG_BLOCK,&newmask,&oldmask);//信号掩码字掩码//3.定时n秒,SIGALRM信号报警(nsecs);/*4。构造临时有效阻塞信号集调用sigsuspend,*UnblockSIGALRMinthetemporaryblockingsignalset*/suspmask=oldmask;sigdelset(&suspmask,SIGALRM);/*5.在sigsuspend的调用过程中,使用临时阻塞信号集suspmask替换原来的阻塞信号集*该信号集不包含SIGALRM信号,同时挂起等待,*当sigsuspend被信号唤醒并返回时,恢复原来的阻塞信号集*/sigsuspend(&suspmask);unslept=alarm(0);//6.恢复原来SIGALRM的处理动作,呼应前面的注释1sigaction(SIGALRM,&oldact,NULL);//7.解除对SIGALRM的阻塞,与之前的评论相呼应2sigprocmask(SIG_SETMASK,&oldmask,NULL);返回(未睡);}intmain(void){while(1){mysleep(2);printf("Twosecondsspassed\n");}return0;}##reentrantfunction/non-reentrantfunction调用执行过程中没有调用过的函数有时,由于一定的时机,函数被重复调用。这种情况称为“重入”。在这个过程中,函数所依赖的环境没有发生变化,所以称函数是可重入的,否则就不是可重入的。如果要使函数可重入,则函数中不能包含全局变量和静态变量,也不能使用malloc或free。本文经授权转载自公众号“良墟Linux”。世界500强外企Linux开发工程师梁旭,在公众号分享大量Linux干货,欢迎关注!