共计 3681 个字符,预计需要花费 10 分钟才能阅读完成。
时序竞态
什么是时序竞态?将同一个程序执行两次,失常状况下,前后两次执行失去的后果应该是一样的。但因为系统资源竞争的起因,前后两次执行的后果有可能失去不一样的后果,这个景象就是 时序竞态。
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}
9
10unsigned int mysleep(unsigned int nsecs)
11{
12 unsigned int unslept;
13
14 signal(SIGALRM, &sig_alrm);
15 unslept = alarm(nsecs);
16 pause();
17
18 return unslept;
19}
20
21
22int main(void)
23{24 while(1){25 mysleep(2);
26 printf("Two seconds passed\n");
27 }
28
29 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}
9
10unsigned int mysleep(unsigned int nsecs)
11{
12 struct sigaction newact, oldact;
13 sigset_t newmask, oldmask, suspmask;
14 unsigned int unslept;
15
16 //1. 为 SIGALRM 设置捕获函数,一个空函数
17 newact.sa_handler = sig_alrm;
18 sigemptyset(&newact.sa_mask);
19 newact.sa_flags = 0;
20 sigaction(SIGALRM, &newact, &oldact);
21
22 //2. 设置阻塞信号集,阻塞 SIGALRM 信号
23 sigemptyset(&newmask);
24 sigaddset(&newmask, SIGALRM);
25 sigprocmask(SIG_BLOCK, &newmask, &oldmask); // 信号屏蔽字 mask
26
27 //3. 定时 n 秒,到时后能够产生 SIGALRM 信号
28 alarm(nsecs);
29
30 /*4. 结构一个调用 sigsuspend 长期无效的阻塞信号集,31 * 在长期阻塞信号集里解除 SIGALRM 的阻塞 */
32 suspmask = oldmask;
33 sigdelset(&suspmask, SIGALRM);
34
35 /*5.sigsuspend 调用期间,采纳长期阻塞信号集 suspmask 替换原有阻塞信号集
36 * 这个信号集中不蕴含 SIGALRM 信号, 同时挂起期待,37 * 当 sigsuspend 被信号唤醒返回时,复原原有的阻塞信号集 */
38 sigsuspend(&suspmask);
39
40 unslept = alarm(0);
41 //6. 复原 SIGALRM 原有的解决动作,响应后面正文 1
42 sigaction(SIGALRM, &oldact, NULL);
43
44 //7. 解除对 SIGALRM 的阻塞,响应后面正文 2
45 sigprocmask(SIG_SETMASK, &oldmask, NULL);
46
47 return(unslept);
48}
49
50int main(void)
51{52 while(1){53 mysleep(2);
54 printf("Two seconds passed\n");
55 }
56
57 return 0;
58}
可重入函数 / 不可重入函数
一个函数在被调用执行期间尚未调用完结的时候,因为某种时序,该函数又被反复调用,这种状况称为「重入」。如果从信号处理程序返回,则继续执行过程断点处的失常指令序列,从从新复原到断点从新执行的过程中,函数所依赖的环境没有产生扭转,就说这个函数是可重入的,反之就是不可重入的。
如果要将函数做成可重入函数,则函数内不能含有全局变量及 static 变量,也不能应用 malloc、free。
更多精彩内容,请关注公众号 良许 Linux,公众内回复 1024 可收费取得 5T 技术材料,包含:Linux,C/C++,Python,树莓派,嵌入式,Java,人工智能 ,等等。公众号内回复 进群,邀请您进高手如云技术交换群。
最初,最近很多小伙伴找我要Linux 学习路线图,于是我依据本人的教训,利用业余时间熬夜肝了一个月,整顿了一份电子书。无论你是面试还是自我晋升,置信都会对你有帮忙!
收费送给大家,只求大家金指给我点个赞!
电子书 | Linux 开发学习路线图
也心愿有小伙伴能退出我,把这份电子书做得更完满!
有播种?心愿老铁们来个三连击,给更多的人看到这篇文章
举荐浏览:
- 干货 | 程序员进阶架构师必备资源免费送
- 神器 | 反对搜寻的资源网站