乐趣区

关于linux:Linux系统编程-时序竞态

时序竞态

什么是时序竞态?将同一个程序执行两次,失常状况下,前后两次执行失去的后果应该是一样的。但因为系统资源竞争的起因,前后两次执行的后果有可能失去不一样的后果,这个景象就是 时序竞态

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 程序。这个函数有可能是上面的时序:

  1. SIGALRM 默认动作是终止过程,因而咱们要将其捕获,对 SIGALRM 注册信号处理函数;
  2. 调用 alarm(1)函数定时 1 秒钟;
  3. alarm(1)调用完结,定时器开始计时。就在这时,过程失去 CPU,进入就绪态期待 CPU(相当于被同学叫醒去打球)。失去 CPU 的形式有可能是内核调度了优先级更高的过程取代了以后过程,使得以后过程无奈取得 CPU;
  4. 咱们晓得,alarm 函数如果采纳天然定时法的话,定时器将始终计时,与过程状态无关。于是,1 秒后,闹钟定时工夫到,内核向以后过程发送 SIGALRM 信号。高优先级过程尚未执行结束,以后过程依然无奈取得 CPU,持续处于就绪态,信号无奈解决(处于未决状态);
  5. 优先级高的过程执行结束,以后过程取得 CPU 资源,内核调度回以后过程执行。SIGALRM 信号递达,并被过程解决;
  6. 信号处理结束后,返回以后主控流程,并调用 pause()函数,挂起期待 alarm 函数发送的 SIGALRM 信号将本人唤醒;
  7. 但理论 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 开发学习路线图

也心愿有小伙伴能退出我,把这份电子书做得更完满!

有播种?心愿老铁们来个三连击,给更多的人看到这篇文章

举荐浏览:

  • 干货 | 程序员进阶架构师必备资源免费送
  • 神器 | 反对搜寻的资源网站
退出移动版