zoukankan      html  css  js  c++  java
  • 信号引起的竞态

    竞态是指设备或系统出现不恰当的执行时序,而得到不正确的结果,由于时间片,或其他因素,导致该到达并响应的信号没有被响应,这就是由信号引起的竞态。 

    对于上面,因为alarm5秒后悔触发信号,pause收到信号后,会结束挂起,再次之前先输出hello

    对于加上sleep(6)则输出hello后一直挂起,因为alarm触发信号的时候,正处于睡眠状态,所以pause收不到信号

     1 #include <unistd.h>
     2 #include <signal.h>
     3 #include <stdio.h>
     4 void sig_alrm(int signo)
     5 {//信号处理函数中什么都不做
     6     /* nothing to do */
     7 }
     8 unsigned int mysleep(unsigned int nsecs)
     9 {
    10     //newact用于保存新的信号处理动作,oldact用于保存原有信号处理动作
    11     struct sigaction newact, oldact;
    12     unsigned int unslept;
    13 
    14     newact.sa_handler = sig_alrm;  //设置信号处理函数
    15     sigemptyset(&newact.sa_mask);
    16     newact.sa_flags = 0;
    17     sigaction(SIGALRM, &newact, &oldact);  //设置新sigaction,保存旧sigaction 
    18 
    19     alarm(nsecs);   //设置定时
    20     pause();        //挂起进程
    21 
    22     unslept = alarm(0);
    23     sigaction(SIGALRM, &oldact, NULL);   //将原有sigaction设置回去
    24 
    25     return unslept;  //返回还未睡够的时间
    26 }
    27 int main(void)
    28 {
    29     while(1){
    30         mysleep(2);
    31         printf("Two seconds passed
    ");
    32     }
    33     return 0;
    34 }

    上述程序的执行过程就是: 
      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返回时,进程的信号屏蔽字恢复为原来的值
     1 #include <unistd.h>
     2 #include <signal.h>
     3 #include <stdio.h>
     4 
     5 void sig_alrm(int signo)
     6 {
     7     /* do  nothing */
     8 }
     9 
    10 unsigned int mysleep(unsigned int nsecs)
    11 {
    12 
    13     struct sigaction newact, oldact;  //newact用于保存新的信号处理动作,oldact用于保存原有信号处理动作
    14 
    15     //newmask将要添加的阻塞信号集,oldmask原有信号集
    16     //suspmask为sigsuspend所用阻塞信号集
    17     sigset_t newmask, oldmask, suspmask;
    18 
    19     unsigned int unslept;//保存未睡够的时间
    20 
    21     /* 设置SIGALRM信号处理函数,并保存原有信号处理函数 */
    22     newact.sa_handler = sig_alrm;
    23     sigemptyset(&newact.sa_mask);
    24     newact.sa_flags = 0;
    25     sigaction(SIGALRM, &newact, &oldact);
    26 
    27     /* 阻塞SIGALRM信号,并保存当前的阻塞信号集 */
    28     sigemptyset(&newmask);//初始化信号集(全部置0),防止原有垃圾值
    29     sigaddset(&newmask, SIGALRM);//阻塞SIGALRM信号
    30     //注册信号屏蔽字
    31     //设原有的信号屏蔽字为mask,则由于以SIG_BLOCK方式
    32     //首先将原有的保存到oldmask中,现在的阻塞信号字为mask=mask | newmask)
    33     sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    34 
    35     alarm(nsecs);//调用时钟,nsecs秒后向该进程发送SIGALRM信号,程序继续执行
    36 
    37     /* 如果这个时候cpu被抢占>nsecs秒,由于SIGALRM信号被阻塞,所以即使时间到了发送了SIGALRM信号,也不会被处理,也就不会发生时序竞争 */
    38     suspmask = oldmask;//将原有信号集赋给sigsuspend所用的信号集
    39     sigdelset(&suspmask, SIGALRM);//在sigsuspend中不阻塞SIGALRM信号
    40 
    41     //挂起
    42     sigsuspend(&suspmask);
    43     //只要有任何信号被捕捉就会被唤醒继续执行下面的程序
    44     //并将信号集恢复为该进程的信号集:sigprocmask之后的信号集
    45 
    46     // 如果在正常的SIGALRM信号到来之前,接收到其他信号,则计算未睡够的时间
    47     unslept = alarm(0);
    48     sigaction(SIGALRM, &oldact, NULL); //恢复愿来SIGALRM信号的动作
    49     sigprocmask(SIG_SETMASK, &oldmask, NULL);//恢复最初信号集
    50     return(unslept);//返回未睡够的时间
    51 }
    52 
    53 int main(void)
    54 {
    55     while(1)
    56     {
    57         mysleep(2);
    58         printf("Two seconds passed
    ");
    59     }
    60     return 0;
    61 }
  • 相关阅读:
    Android开发(十五)-Service和BroadcastReceiver
    Android开发(十四)-ContentProvider数据共享
    Android开发(十三)-图形与图像处理
    Android开发(十二)-Android应用资源
    Android开发(十一)-Intent和IntentFilter通信
    Android开发(十)-Activity和Fragment
    Android开发(九)-事件机制
    模拟面试
    二叉堆
    面试
  • 原文地址:https://www.cnblogs.com/13224ACMer/p/6408173.html
Copyright © 2011-2022 走看看