Unix/Linux编程:竞态条件与sigsuspend函数 向右看齐 2022-10-16 06:14 151阅读 0赞 # 利用pause和alarm函数实现sleep # #include <unistd.h> int pause(void); pause函数使调用进程挂起直到有信号到达。如果信号的处理动作是终止进程,那么进程终止,pause()函数没有机会返回;如果信号的处理动作是忽略,那么进程继续出于挂起状态,pause()不放。如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno被设置为EINTR,所以pause只有出错的返回值。错误码EINTR表示“被信号中断” alam函数请参考[这里][Link 1] 下面使用pause和alarm实现sleep(3)函数,称为mysleep: #include<stdio.h> #include<signal.h> #include<unistd.h> void sig_alrm(int signo) { /* nothing to do */ } unsigned int mysleep(unsigned int nsecs){ struct sigaction newact, oldact; unsigned int unslept; newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); alarm(nsecs); pause(); unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); return unslept; } int main(int argc, char *argv[]) { while (1) { mysleep(2); printf("Two seconds passed\n"); } return 0; } * main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig\_alrm; * 调用alarm(nsecs)设定闹钟 * 调用pause等待,内核切换到别的进程运行 * nses秒之后,闹钟超时,内核发SIGALRM给这个进程 * 从内核态返回这个进程的用户态之前需要处理未决信号,发现有SIGALRM信号,其处理函数是sig\_alrm * 切换到用户态执行sig\_alrm函数,进行sig\_alrm函数时SIGALRM信号被自动屏蔽,从sig\_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。 * pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作 需要注意的是虽然sig\_alrm函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。此外,mysleep函数的返回值表示“未睡到”的时间,即unslept,当尚未计时到nsecs而pause函数先被其他信号处理函数所中断返回,在外界看来就是在sleep期间被其他信号处理函数中断了,则mysleep返回非0值,即unslept。当然如果是被SIGALRM handler所中断,则表示睡眠时间到,mysleep返回值为0。 # 竞态条件与sigsuspend函数 # 现在重新审视上面的mysleep函数,设想这样的时序 * 注册SIGALRM信号的处理函数 * 调用alarm(usecs)设定闹钟 * 内核调用优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个又要执行很长时间 * nsecs秒后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态 * 优先级更高的进程执行完了,内核要调度会这个进程执行,SIGARM信号递达,执行处理函数sig\_alrm之后再次进入内核 * 返回这个进程的主控制流程,alarm(nsecs)返回,调用pause()挂起等待。 * 可是SIGALRM信号已经处理完了,还等待什么呢? 出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(nsecs)之后的nsecs秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件(Race Condition)。 如何解决上述问题呢?我们可能会想到,在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。看看以下方法可行吗? 1. 屏蔽SIGALRM信号; 2. alarm(nsecs); 3. 解除对SIGALRM信号的屏蔽; 4. pause(); 从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这个间隙,我们把解除屏蔽移到pause后面可以吗? 1. 屏蔽SIGALRM信号; 2. alarm(nsecs); 3. pause(); 4. 解除对SIGALRM信号的屏蔽; 这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。**要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend函数的功能**。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。 #include <signal.h> int sigsuspend(const sigset_t *sigmask); 和pause一样,suspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。 以下用sigsuspend重新实现mysleep函数: #include<stdio.h> #include<signal.h> #include<unistd.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; /* set our handler, save previous information */ newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); /* block SIGALRM and save current signal mask */ sigemptyset(&newmask); sigaddset(&newmask, SIGALRM); sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(nsecs); suspmask = oldmask; sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't block */ sigsuspend(&suspmask); /* wait for any signal to be caught */ /* some signal has been caught. SIGALRM is now blocked */ unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); /* reset previous action */ /* reset signal mask, which unblocks SIGALRM */ sigprocmask(SIG_SETMASK, &oldmask, NULL); return(unslept); } int main(void) { while (1) { mysleep(2); printf("Two seconds passed\n"); } return 0; } 如果在调用mysleep函数时SIGALRM信号没有屏蔽: 1. 调用sigprocmask(SIG\_BLOCK, &newmask, &oldmask);时屏蔽SIGALRM。 2. 调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。 3. SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。 4. 调用sigprocmask(SIG\_SETMASK, &oldmask, NULL);时再次解除对SIGALRM的屏蔽。 [Link 1]: https://blog.csdn.net/zhizhengguan/article/details/111828242
相关 并发编程:线程同步与竞态条件实例 并发编程是多任务同时执行的编程方式。其中,线程同步和竞态条件是常见的问题。 1. 线程同步: - **互斥锁**(Mutex):一个线程在持有锁后可以访问临界区,其他线 àì夳堔傛蜴生んèń/ 2024年09月12日 15:03/ 0 赞/ 16 阅读
相关 Java并发编程中的竞态条件实例 在Java并发编程中,竞态条件是指两个或多个线程对共享资源的访问没有按照预期的顺序执行,导致结果不可预测。 下面是一个简单的竞态条件实例: ```java // 定义一个共 妖狐艹你老母/ 2024年09月10日 23:30/ 0 赞/ 21 阅读
相关 Java多线程编程:竞态条件问题 在Java多线程编程中,竞态条件(Race Condition)是一种常见的并发问题。它主要表现为对共享资源的不正确访问,导致多个线程同时修改结果,从而产生无法预期的行为。 比眉伴天荒/ 2024年09月10日 22:39/ 0 赞/ 24 阅读
相关 Unix/Linux编程:竞态条件与sigsuspend函数 利用pause和alarm函数实现sleep include <unistd.h> int pause(void); pause函数使调用进程 向右看齐/ 2022年10月16日 06:14/ 0 赞/ 152 阅读
相关 并发编程五:竞态条件与临界区 并发编程:竞态条件与临界区 介绍 当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竟态条件发生的代码称作临界区。 浅浅的花香味﹌/ 2022年09月25日 05:26/ 0 赞/ 156 阅读
相关 竞态条件与临界区 在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。 如,同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件。 约定不等于承诺〃/ 2022年05月27日 03:12/ 0 赞/ 156 阅读
相关 竞态条件漏洞简介 1 竞态条件漏洞 下面的代码段属于某个特权程序(即 Set-UID 程序),它使用 Root 权限运行。 if (!access("/tmp/X", W_OK) 浅浅的花香味﹌/ 2021年07月28日 17:20/ 0 赞/ 408 阅读
还没有评论,来说两句吧...