Linux开发--时序竞态与解决办法
来源:互联网 发布:上海菜鸟网络公司地址 编辑:程序博客网 时间:2024/06/14 20:43
一、信号引起的竞态
竞态是指设备或系统出现不恰当的执行时序,而得到不正确的结果,由于时间片,或其他因素,导致该到达并响应的信号没有被响应,这就是由信号引起的竞态。
假设我们要写一个sleep函数,其中利用到了信号,编写的过程如下:
1.注册一个信号signal(SIGALRM,handler)。接收内核给出的一个信号。
2.调用alarm()函数。
3.pause()挂起进程。(int pause(void)使调用进程挂起,直到有信号递达,如果递达信号是忽略,则继续挂起)
实现如下:
#include <unistd.h>#include <signal.h>#include <stdio.h>void sig_alrm(int signo){//信号处理函数中什么都不做 /* nothing to do */}unsigned int mysleep(unsigned int nsecs){ //newact用于保存新的信号处理动作,oldact用于保存原有信号处理动作 struct sigaction newact, oldact; unsigned int unslept; newact.sa_handler = sig_alrm; //设置信号处理函数 sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); //设置新sigaction,保存旧sigaction alarm(nsecs); //设置定时 pause(); //挂起进程 unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); //将原有sigaction设置回去 return unslept; //返回还未睡够的时间}int main(void){ while(1){ mysleep(2); printf("Two seconds passed\n"); } return 0;}
上述述程序的执行过程就是:
1、先注册SIGALRM信号的处理函数(在信号处理函数中什么都不做),并将原有sigaction保存起来。
2、开始定时,挂起进程。
3、2秒之后发送SIGALRM信号,进程响应信号,被唤起,进入什么都不做的信号处理函数。
4、执行完信号处理函数之后恢复原有sigaction,结束sleep
5、打印Two seconds passed
但是在这里有一个问题,如果在“alarm(nsecs); 设置定时”和 “pause(); 挂起进程”之间由于时间片被用完,导致该进程被调出(这时alarm函数已经被执行,内核已经开始计时),而且在该系统中进程数很多,导致“饥饿现象”,所以该进程很久没有被掉入,假设三秒都没有被掉入(我们设置的定时是2秒),这时在内核中其实在它被掉入前已经倒数完毕并发出SIGALRM信号,所以执行它的信号处理函数(什么都不做),然后它在第三秒后被掉入,继续执行,这时执行的函数是pause(),进程被挂起,但是又由于alarm发出的SIGALRM信号在之前已经被执行,所以pause()会永远等待不到SIGALRM信号的到达,那么该进程会永远的被挂起!
以上的情况就是一种由信号引起的竞态,这种情况是不可预知的,而且如若出现后果很严重!
所以,要改变上述情况,可以使用sigsuspend函数,这个函数有一个特点就是以下的第1步和第2步是一个原子操作,这时,在使用alarm()函数之前先阻塞SIGALRM信号,在执行sigsuspend时解除阻塞(以下的第一步就是该函数的作用),这样就不会引起竞态!就可以解决上述问题了!
#include <signal.h>int sigsuspend(const sigset_t *mask)函数的执行过程是:1.以通过指定mask来临时解除对某个信号的屏蔽,2.然后挂起等待,3.当被信号唤醒sigsuspend返回时,进程的信号屏蔽字恢复为原来的值
改进之后的例子:
#include <unistd.h>#include <signal.h>#include <stdio.h>void sig_alrm(int signo){ /* do nothing */}unsigned int mysleep(unsigned int nsecs){ struct sigaction newact, oldact; //newact用于保存新的信号处理动作,oldact用于保存原有信号处理动作 //newmask将要添加的阻塞信号集,oldmask原有信号集 //suspmask为sigsuspend所用阻塞信号集 sigset_t newmask, oldmask, suspmask; unsigned int unslept;//保存未睡够的时间 /* 设置SIGALRM信号处理函数,并保存原有信号处理函数 */ newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); /* 阻塞SIGALRM信号,并保存当前的阻塞信号集 */ sigemptyset(&newmask);//初始化信号集(全部置0),防止原有垃圾值 sigaddset(&newmask, SIGALRM);//阻塞SIGALRM信号 //注册信号屏蔽字 //设原有的信号屏蔽字为mask,则由于以SIG_BLOCK方式 //首先将原有的保存到oldmask中,现在的阻塞信号字为mask=mask | newmask) sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(nsecs);//调用时钟,nsecs秒后向该进程发送SIGALRM信号,程序继续执行 /* 如果这个时候cpu被抢占>nsecs秒,由于SIGALRM信号被阻塞,所以即使时间到了发送了SIGALRM信号,也不会被处理,也就不会发生时序竞争 */ suspmask = oldmask;//将原有信号集赋给sigsuspend所用的信号集 sigdelset(&suspmask, SIGALRM);//在sigsuspend中不阻塞SIGALRM信号 //挂起 sigsuspend(&suspmask); //只要有任何信号被捕捉就会被唤醒继续执行下面的程序 //并将信号集恢复为该进程的信号集:sigprocmask之后的信号集 // 如果在正常的SIGALRM信号到来之前,接收到其他信号,则计算未睡够的时间 unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); //恢复愿来SIGALRM信号的动作 sigprocmask(SIG_SETMASK, &oldmask, NULL);//恢复最初信号集 return(unslept);//返回未睡够的时间}int main(void){ while(1) { mysleep(2); printf("Two seconds passed\n"); } return 0;}
- Linux开发--时序竞态与解决办法
- Linux--信号时序竞态
- [Linux驱动开发] Nand Flash时序图分析
- s5pv210开发板 linux LCD液晶屏 时序分析
- SDRAM原理与时序
- FPGA各种时序问题的解决办法
- ssh2开源框架开发“登陆”功能的类图与时序图
- 功能仿真与时序仿真
- SDRAM工作时序与原理
- DDR工作时序与原理
- ddr2 工作时序与原理
- ram同步与异步时序
- 组合逻辑与时序逻辑
- SDRAM时序理解与操作
- SDRAM工作时序与原理
- 时序
- linux嵌入式开发平台网卡驱动解决办法
- net 开发遇到的问题与解决办法
- 枚举类
- SpringMvc+Spring4+hibernate框架 ajax提交JSON数据Controller接收
- Leetcode500. Keyboard Row
- 图
- 阿里云服务器忘记数据库(mysql 5.7)密码改咋整
- Linux开发--时序竞态与解决办法
- intellij idea + spring boot + mybatis + druid + maven + mysql + thymeleaf
- 547. Friend Circles
- PYTHON中UDP,socket的使用。
- 9.高级控件(三)之 Gallery及ViewPager
- Leetcode617. Merge Two Binary Trees
- vedioview加载本地视频播放
- hibernate
- C++11并发开篇