竞态条件(时序竞态):
pause 函数
调用该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃 cpu) 直 到有信号递达将其唤醒,等不到一直等
int pause(void);
返回值:-1 并设置 errno 为 EINTR
返回值:
- 如果信号的默认处理动作是终止进程,则进程终止,pause 函数么有机会返回。
- 如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause 函数不返回。
- 如果信号的处理动作是捕捉,则【调用完信号处理函数之后,pause 返回-1】 errno 设置为 EINTR,表示“被信号中断”。想想我们还有哪个函数只有出错返回值。
- pause 收到的信号不能被屏蔽,如果被屏蔽,那么 pause 就不能被唤醒。
使用 pause 和 alarm 来实现 sleep 函数。
/** 模拟实现sleep*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<errno.h>
void catch_sigalrm(int signo)//捕捉函数 保证pause不会被杀死
{;
}unsigned int mysleep(unsigned int seconds)
{int ret;struct sigaction act,oldact;act.sa_handler = catch_sigalrm;sigemptyset(&act.sa_mask);act.sa_flags = 0;ret = sigaction(SIGALRM,&act,&oldact);//注册捕捉函数 改变了SIGALRM信号的设置if(ret == -1){perror("sigaction error");exit(1);} alarm(seconds); ret = pause();//主动挂起 等信号if(ret == -1 && errno == EINTR){printf("pause sucess\n");} alarm(0);//将闹钟清零sigaction(SIGALRM,&oldact,NULL);//恢复SIGALRN信号旧有的处理方式return ret;
}
int main(void)
{while(1){mysleep(3);printf("-----------------\n");}return 0;
}
时序竞态
欲睡觉,定闹钟 10 分钟,希望 10 分钟后闹铃将自己唤醒。 正常:定时,睡觉,10 分钟后被闹钟唤醒。
异常:闹钟定好后,被唤走,外出劳动,20 分钟后劳动结束。回来继续睡觉计划,但劳动期间闹钟已经响过, 不会再将我唤醒。
时序问题分析
借助 pause 和 alarm 实现的 mysleep 函数。设想如下时序:
- 注册 SIGALRM 信号处理函数 (sigaction…) 2
- 调用 alarm(1) 函数设定闹钟 1 秒。
- 函数调用刚结束,开始倒计时 1 秒。当前进程失去 cpu,内核调度优先级高的进程(有多个)取代当前进程。 当前进程无法获得 cpu,进入就绪态等待 cpu。
- 1 秒后,闹钟超时,内核向当前进程发送 SIGALRM 信号(自然定时法,与进程状态无关),高优先级进程尚未 执行完,当前进程仍处于就绪态,信号无法处理(未决)
- 优先级高的进程执行完,当前进程获得 cpu 资源,内核调度回当前进程执行。SIGALRM 信号递达,信号设置 捕捉,执行处理函数 sig_alarm。
- 信号处理函数执行结束,返回当前进程主控流程, pause()被调用挂起等待。 (欲等待alarm函数发送的SIGALRM 信号将自己唤醒)
- SIGALRM 信号已经处理完毕,pause 不会等到。
如何解决时序问题(重点)
可以通过设置屏蔽 SIGALRM 的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽” 与“挂起等待信号”这个两个操作间隙失去 cpu 资源。除非将这两步骤合并成一个“原子操作”。sigsuspend 函数具 备这个功能。在对时序要求严格的场合下都应该使用 sigsuspend 替换 pause。
int sigsuspend(const sigset_t* mask);
挂起等待信号。
sigsuspend 函数调用期间,进程信号屏蔽字由其参数 mask 指定。
可将某个信号(如 SIGALRM)从临时信号屏蔽字 mask 中删除,这样在调用 sigsuspend 时将解除对该信号的屏 蔽,然后挂起等待, 当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值。 如果原来对该信号是屏蔽态, sigsuspend 函数返回后仍然屏蔽该信号。
总结
- 竞态条件,跟系统负载有很紧密的关系,体现出信号的不可靠性。系统负载越严重,信号不可靠性越强。
- 不可靠由其实现原理所致**。信号是通过软件方式实现(跟内核调度高度依赖,延时性强),每次系统调用结束后, 或中断处理处理结束后,需通过扫描 PCB 中的未决信号集,来判断是否应处理某个信号。**当系统负载过重时,会出 现时序混乱。
- 这种意外情况只能在编写程序过程中,提早预见,主动规避,而无法通过 gdb 程序调试等其他手段弥补。且由 于该错误不具规律性,后期捕捉和重现十分困难