时序竞态
什么是时序竞态?将同一个程序执行两次,失常状况下,前后两次执行失去的后果应该是一样的。但因为系统资源竞争的起因,前后两次执行的后果有可能失去不一样的后果,这个景象就是时序竞态。
pause函数
函数原型:
int pause(void);
函数作用:
过程调用pause函数时,会造成过程被动挂起(处于阻塞状态,并被动放弃CPU),并且期待信号将其唤醒。
返回值:
咱们晓得,信号的解决形式有三种:1. 默认动作;2. 疏忽解决;3. 捕获。过程收到一个信号后,会先解决响应信号,再唤醒pause函数。于是有上面几种状况:
① 如果信号的默认解决动作是终止过程,则过程将被终止,也就是说一收到信号过程就终止了,pause函数基本就没有机会返回;
② 如果信号的默认解决动作是疏忽,则过程将间接疏忽该信号,相当于没收到这个信号,过程持续处于挂起状态,pause函数不返回;
③ 如果信号的解决动作是捕获,则过程调用完信号处理函数之后,pause返回-1,errno设置为EINTR,示意“被信号中断”。
④ pause收到的信号不能被屏蔽,如果被屏蔽,那么pause就不能被唤醒。
因为alarm函数能够在设定的工夫之后发送SIGALRM信号,pause函数又能够将过程挂起期待信号,则二者联合能够本人写一个sleep函数,如下:
1#include <unistd.h> 2#include <signal.h> 3#include <stdio.h> 4 5void sig_alrm(int signo) 6{ 7 /* nothing to do */ 8} 910unsigned int mysleep(unsigned int nsecs)11{12 unsigned int unslept;1314 signal(SIGALRM, &sig_alrm);15 unslept = alarm(nsecs); 16 pause();1718 return unslept;19}202122int main(void)23{24 while(1){25 mysleep(2);26 printf("Two seconds passed\n");27 }2829 return 0;30}
时序竞态前导例
在讲时序竞态具体景象之前,咱们先来看一个生存中常见的场景:
想午睡10分钟,于是定了个10分钟的闹钟,心愿10分钟后闹钟将本人叫醒。
失常状况:定好闹钟,午睡,10分钟后闹钟叫醒本人;
异常情况:定好闹钟,躺下睡觉2分钟,被同学叫醒去打球,打了20分钟后回来持续睡觉。但在打球期间,闹钟早就响过了,将不会再唤醒本人。
这个例子与之后要讲的时序竞态有很大的相似之处。
时序竞态问题剖析
咱们再回过头来看下面所写的mysleep程序。这个函数有可能是上面的时序:
- SIGALRM默认动作是终止过程,因而咱们要将其捕获,对SIGALRM注册信号处理函数;
- 调用alarm(1)函数定时1秒钟;
- alarm(1)调用完结,定时器开始计时。就在这时,过程失去CPU,进入就绪态期待CPU(相当于被同学叫醒去打球)。失去CPU的形式有可能是内核调度了优先级更高的过程取代了以后过程,使得以后过程无奈取得CPU;
- 咱们晓得,alarm函数如果采纳天然定时法的话,定时器将始终计时,与过程状态无关。于是,1秒后,闹钟定时工夫到,内核向以后过程发送SIGALRM信号。高优先级过程尚未执行结束,以后过程依然无奈取得CPU,持续处于就绪态,信号无奈解决(处于未决状态);
- 优先级高的过程执行结束,以后过程取得CPU资源,内核调度回以后过程执行。SIGALRM信号递达,并被过程解决;
- 信号处理结束后,返回以后主控流程,并调用pause()函数,挂起期待alarm函数发送的SIGALRM信号将本人唤醒;
- 但理论SIGALRM信号曾经处理完毕,pause()函数永远不会等到。
解决时序竞态问题
通过以上时序剖析,咱们能够看出,造成时序竞态的起因就是SIGALRM信号在过程失去CPU的时候就曾经发送过去。为了避免这个景象呈现,咱们能够先将该信号阻塞,将其“抓住”,再在解除阻塞的时候立即调用pause函数挂起期待。这样即便在调用alarm就失去CPU,也能够在过程从新取得CPU时将抓到的SIGALRM信号从新“放进去”,并将之后的pause函数唤醒。
但在解除阻塞与pause期待挂起信号之间,还是有可能失去CPU,除非将这两个步骤做成一个“原子操作”。Linux零碎提供的sigsuspend函数就具备这个性能。所以,在时序要求比拟严格的场合下都应该应用sigsuspend函数,而非pause函数。
函数原型:
int sigsuspend(const sigset_t *mask);
函数作用:
挂起期待信号;
函数参数:
mask,传入参数,sigsuspend函数调用期间,过程信号屏蔽字由参数mask指定。
具体用法:可将某个信号(如SIGALRM)从长期信号屏蔽字mask中删除,也就是在调用sigsuspend函数时对该信号解除屏蔽,而后挂起期待信号。但咱们此时曾经扭转了过程的信号屏蔽字,所以调用完sigsuspend函数之后,应将过程的信号屏蔽字复原原样。
1#include <unistd.h> 2#include <signal.h> 3#include <stdio.h> 4 5void sig_alrm(int signo) 6{ 7 /* nothing to do */ 8} 910unsigned int mysleep(unsigned int nsecs)11{12 struct sigaction newact, oldact;13 sigset_t newmask, oldmask, suspmask;14 unsigned int unslept;1516 //1.为SIGALRM设置捕获函数,一个空函数17 newact.sa_handler = sig_alrm;18 sigemptyset(&newact.sa_mask);19 newact.sa_flags = 0;20 sigaction(SIGALRM, &newact, &oldact);2122 //2.设置阻塞信号集,阻塞SIGALRM信号23 sigemptyset(&newmask);24 sigaddset(&newmask, SIGALRM);25 sigprocmask(SIG_BLOCK, &newmask, &oldmask); //信号屏蔽字 mask2627 //3.定时n秒,到时后能够产生SIGALRM信号28 alarm(nsecs);2930 /*4.结构一个调用sigsuspend长期无效的阻塞信号集,31 * 在长期阻塞信号集里解除SIGALRM的阻塞*/32 suspmask = oldmask;33 sigdelset(&suspmask, SIGALRM);3435 /*5.sigsuspend调用期间,采纳长期阻塞信号集suspmask替换原有阻塞信号集36 * 这个信号集中不蕴含SIGALRM信号,同时挂起期待,37 * 当sigsuspend被信号唤醒返回时,复原原有的阻塞信号集*/38 sigsuspend(&suspmask); 3940 unslept = alarm(0);41 //6.复原SIGALRM原有的解决动作,响应后面正文142 sigaction(SIGALRM, &oldact, NULL);4344 //7.解除对SIGALRM的阻塞,响应后面正文245 sigprocmask(SIG_SETMASK, &oldmask, NULL);4647 return(unslept);48}4950int main(void)51{52 while(1){53 mysleep(2);54 printf("Two seconds passed\n");55 }5657 return 0;58}
可重入函数/不可重入函数
一个函数在被调用执行期间尚未调用完结的时候,因为某种时序,该函数又被反复调用,这种状况称为「重入」。如果从信号处理程序返回,则继续执行过程断点处的失常指令序列,从从新复原到断点从新执行的过程中,函数所依赖的环境没有产生扭转,就说这个函数是可重入的,反之就是不可重入的。
如果要将函数做成可重入函数,则函数内不能含有全局变量及static变量,也不能应用malloc、free。
更多精彩内容,请关注公众号良许Linux,公众内回复1024可收费取得5T技术材料,包含:Linux,C/C++,Python,树莓派,嵌入式,Java,人工智能,等等。公众号内回复进群,邀请您进高手如云技术交换群。
最初,最近很多小伙伴找我要Linux学习路线图,于是我依据本人的教训,利用业余时间熬夜肝了一个月,整顿了一份电子书。无论你是面试还是自我晋升,置信都会对你有帮忙!
收费送给大家,只求大家金指给我点个赞!
电子书 | Linux开发学习路线图
也心愿有小伙伴能退出我,把这份电子书做得更完满!
有播种?心愿老铁们来个三连击,给更多的人看到这篇文章
举荐浏览:
- 干货 | 程序员进阶架构师必备资源免费送
- 神器 | 反对搜寻的资源网站