丹徒网站建设咨询,有用模板网在线制作免费网站,西安大雁塔的来历,上海人才引进政策接上一篇#xff1a;linux_信号捕捉-signal函数-sigaction函数-sigaction结构体 今天来分享时序竞态的知识#xff0c;关于时序竞态的问题#xff0c;肯定会和cpu有关#xff0c;也会学习两个函数#xff0c;pause函数#xff0c;sigsuspend函数#xff0c; 也会分享什么…接上一篇linux_信号捕捉-signal函数-sigaction函数-sigaction结构体 今天来分享时序竞态的知识关于时序竞态的问题肯定会和cpu有关也会学习两个函数pause函数sigsuspend函数 也会分享什么是可重入函数和不可重入函数话不多说上一碗时序竞态的大菜 此博主在CSDN发布的文章目录【我的CSDN目录作为博主在CSDN上发布的文章类型导读】 在介绍时序竞态之前先介绍一下pause函数。
1.pause函数
函数作用 调用该函数可以造成进程主动挂起等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃cpu) 直到有信号递达将其唤醒。 头文件 #include unistd.h 函数原型 int pause(void); 函数参数 无 返回值 返回值-1 并设置errno为EINTR ① 如果信号的默认处理动作是终止进程则进程终止pause函数么有机会返回。 ② 如果信号的默认处理动作是忽略进程继续处于挂起状态pause函数不返回。 ③ 如果信号的处理动作是捕捉则【调用完信号处理函数之后pause返回-1】 errno设置为EINTR表示“被信号中断”。想想我们还有哪个函数只有出错返回值。 ④ pause收到的信号不能被屏蔽如果被屏蔽那么pause就不能被唤醒。
1.1.例子–pause函数运用
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h
void donothing(int signo)
{
}
unsigned int mysleep(unsigned int seconds)
{unsigned int ret;struct sigaction act, oldact;act.sa_handler donothing;sigemptyset(act.sa_mask);//信号集清零act.sa_flags 0;
//注册信号捕捉函数sigaction(SIGALRM, act, oldact);alarm(seconds); //定时固定的秒数 1 pause(); //挂起ret alarm(0); sigaction(SIGALRM, oldact, NULL); //恢复SIGALRM 默认处理方式return ret;
}
int main(void)
{mysleep(5);return 0;
}2.时序竞态 时序竞态 由于进程之间执行的顺序不同导致同一个进程多次运行后产生了不同结果的现象。 竞态问题总结 竞态条件跟系统负载有很紧密的关系体现出信号的不可靠性。系统负载越严重信号不可靠性越强。 不可靠由其实现原理所致。信号是通过软件方式实现(跟内核调度高度依赖延时性强)每次系统调用结束后或中断处理处理结束后需通过扫描PCB中的未决信号集来判断是否应处理某个信号。当系统负载过重时会出现时序混乱。 这种意外情况只能在编写程序过程中提早预见主动规避而无法通过gdb程序调试等其他手段弥补。且由于该错误不具规律性后期捕捉和重现十分困难。
3.时序竞态问题1-信号处理 为什么会有时序竞态的问题产生是因为cpu在执行进程的时候一个进程只执行一个时间片段所以你写的程序运行的时候有时候执行千次万次看似没什么问题可是某一次突然就崩了当你去查问题的时候复查了很长的时间都没有找到问题这种问题的出现的概念可能是千万分之一不容易发生但一发生就是致命问题而这种问题还不易发现只能通过我们日常写代码的经验来避免。 例如在1.1的例子中在调用alarm函数后失去CPUCPU去执行别的进程了当执行别的进程的时间大于定时的时间后会发生什么问题 如下图。 就这样本能定时1s的程序成了永久阻塞了这种情况是可能发生的而发生的几率可能就是千万分之一。 设想在商业代码中出现这种错误那后果则是毁灭性的。 当然在上述案例中也有解决方法那就是利用信号的屏蔽机制来解决这就得说一下另一个函数sigsuspend了。
3.1.解决时序问题1-sigsuspend函数
函数作用 挂起等待信号。 头文件 #include signal.h 函数原型 int sigsuspend(const sigset_t *mask); 函数参数 mask调用该函数期间决定信号屏蔽字得集合 返回值 错误返回-1并设置errno以指示错误通常为EINTR。 EINTR被一个信号中断。 可以通过设置屏蔽SIGALRM的方法来控制程序执行逻辑但无论如何设置程序都有可能在“解除信号屏蔽”与“挂起等待信号”这个两个操作间隙失去cpu资源。除非将这两步骤合并成一个“原子操作”。sigsuspend函数具备这个功能。在对时序要求严格的场合下都应该使用sigsuspend替换pause。 原子操作cpu在执行这个函数就会把他执行完不会停止
3.2.例子-解决例1.1时序竞态问题1代码
#include unistd.h
#include signal.h
#include stdio.h
void sig_alrm(int signo)
{/* nothing to do */
}
unsigned int mysleep(unsigned int nsecs)
{struct sigaction newact, oldact;sigset_t newmask, oldmask, suspmask;unsigned int unslept;/*为SIGALRM设置捕捉函数一个空函数*/newact.sa_handler sig_alrm;//将信号集清零sigemptyset(newact.sa_mask);newact.sa_flags 0;//注册信号捕捉函数,oldact保留原有的信号集sigaction(SIGALRM, newact, oldact);/*设置阻塞信号集阻塞SIGALRM信号*/sigemptyset(newmask);//将信号集清零sigaddset(newmask, SIGALRM);//将SIGALRM信号加入信号集置1//屏蔽SIGALRM信号设置信号屏蔽字oldmask保留原有的信号集sigprocmask(SIG_BLOCK, newmask, oldmask); //原子操作即调用该函数期间不能失去cpu//定时nsecs秒到时后可以产生SIGALRM信号alarm(nsecs);/*构造一个调用sigsuspend临时有效的阻塞信号集* 在临时阻塞信号集里解除SIGALRM的阻塞*/suspmask oldmask; //sigdelset(suspmask, SIGALRM); //在suspmask集合中清除对SIGALRM函数的屏蔽/*sigsuspend调用期间采用临时阻塞信号集suspmask替换原有阻塞信号集* 这个信号集中不包含SIGALRM信号,同时挂起等待* 当sigsuspend被信号唤醒返回时恢复原有的阻塞信号集*/sigsuspend(suspmask); unslept alarm(0);//恢复SIGALRM原有的处理动作呼应前面注释1sigaction(SIGALRM, oldact, NULL);//解除对SIGALRM的阻塞呼应前面注释2sigprocmask(SIG_SETMASK, oldmask, NULL);return(unslept);
}
int main(void)
{
while(1)
{mysleep(2);printf(Two seconds passed\n);}return 0;
}4.时序竞态问题2-全局变量异步I/O 分析如下父子进程交替数数程序。 当捕捉函数里面的sleep取消程序即会出现问题。 造成该问题出现得原因是什么呢
#include stdio.h
#include signal.h
#include unistd.h
#include stdlib.hint n 0, flag 0;
void sys_err(char *str)
{perror(str);exit(1);
}
void do_sig_child(int num)
{printf(I am child %d\t%d\n, getpid(), n);n 2;flag 1;sleep(1);
}
void do_sig_parent(int num)
{printf(I am parent %d\t%d\n, getpid(), n);n 2;flag 1;sleep(1);
}
int main(void)
{pid_t pid;
struct sigaction act;if ((pid fork()) 0)sys_err(fork);else if (pid 0) { n 1;sleep(1);act.sa_handler do_sig_parent;sigemptyset(act.sa_mask);act.sa_flags 0;sigaction(SIGUSR2, act, NULL); //注册自己的信号捕捉函数 父使用SIGUSR2信号do_sig_parent(0); while (1) {/* wait for signal */;if (flag 1) { //父进程数数完成kill(pid, SIGUSR1);flag 0; //标志已经给子进程发送完信号}}} else if (pid 0) { n 2;act.sa_handler do_sig_child;sigemptyset(act.sa_mask);act.sa_flags 0;sigaction(SIGUSR1, act, NULL);while (1) {/* waiting for a signal */;if (flag 1) {kill(getppid(), SIGUSR2);flag 0;//分析若是在cpu执行到此处时收到父进程得信号在flag还未被改完就去执行do_sig_child该函数会怎么样}}}return 0;
} 示例中通过flag变量标记程序实行进度。flag置1表示数数完成。flag置0表示给对方发送信号完成。 问题出现的位置在父子进程kill函数之后需要紧接着调用 flag将其置0标记信号已经发送。但在这期间很有可能被kernel调度失去执行权利而对方获取了执行时间通过发送信号回调捕捉函数从而修改了全局的flag。 如何解决该问题呢 可以使用后续会分享到的“锁”机制。 当操作全局变量的时候通过加锁、解锁来解决该问题。 现在我们在编程期间如若使用全局变量应在主观上注意全局变量的异步IO可能造成的问题。
5.时序竞态问题3-可/不可重入函数 一个函数在被调用执行期间(尚未调用结束)由于某种时序又被重复调用称之为“重入”。根据函数实现的方法可分为“可重入函数”和“不可重入函数”两种。 可重入函数函数内不能含有全局变量及static变量不能使用malloc、free等。 不可重入函数函数内含有全局变量及static变量使用malloc、free是标准I/O函数。
所以我们的信号捕捉函数应该设计为可重入函数。 信号处理程序可以调用的可重入函数可参阅man 7 signal。
以上就是本次的分享了希望能对广大网友有所帮助。