信号的捕捉及认识竞态条件

本节重点:
1.信号的捕捉(内核如何实现信号的捕捉)
2.信号捕捉的相关函数
3.实现mysleep函数(1)、(2改良版)
4.可重入函数的简单介绍
5.竞态条件与sigsuspend函数
————————————————————————————————————————————————————
1.信号的捕捉(内核如何实现信号的捕捉)
之前我们说过信号被递达之后并不是立即对其进行处理,而是要等到合适的时机才对其进行处理。这个时机就是:内核态—>信号处理—>用户态。
进程对信号递达后的三种处理方式:

1.忽略

2.默认(终止)

3.捕捉
由于前两种的处理较为简单,在这里我们重点来说说第三种方式——信号捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,称为捕捉信号。
下面用一张图来看看内核是如何捕捉信号的:

这里写图片描述
具体的解释如下:举例如下:
1.用户程序注册了某信号的处理函数sighandler。
2.当前正在执行main函数,这时发生中断或异常切换到内核态。
3.在中断处理完毕后要返回用户态的main函数之前检查到有信号递达。
4.内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数(sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系, 是两个独⽴的控制流程)
5.sighandler函数返回后⾃动执⾏特殊的系统调用sigreturn再次进入内核态。
6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执⾏了。

2.信号捕捉的相关函数

  • sigaction函数:
    1.函数原型:
#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
2.函数作用:可以获取和修改与指定信号相关联的处理动作。与signal函数的作用基本相同。

3.返回值:调用成功返回0,出错返回-1.
4.参数解释:
  • signo:指定的信号的编号。
  • act:若act指针非空,则根据act修改该信号的处理动作。
  • oact:若oact指针非空,则通过oact传出该信号原来的处理动作。其与act都指向sigaction的结构体。
    sigaction的结构体如下:
           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

其中成员解释如下:

1.sa_handler是一个常数,将其赋值为SIG_IGN传给sigaction表示忽略信号;赋值为常数SIG_DFL 表示执行系统默认动作;赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册 了一个信号处理函数。

注:当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。

2.如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号。则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

3.sa_flags字段包含⼀些选项,默认设置设为0。

上篇博客中的三张表中,其中有一个为handler:(处理方法表)
其中参数为SIG_DFL:表示执行默认动作(即为终止进程)
SIG_IGN:表示忽略此信号。

下面实现一个用sigaction捕捉信号的代码:

  1 #include<stdio.h>
  2 #include<signal.h>
  3 
  4 void handler(int signo)
  5 {
  6         printf("get a sig %d\n",signo);
  7 }
  8 int main()
  9 {
 10         struct sigaction act,oact;
 11         act.sa_handler=handler;
 12         act.sa_flags=0;
 13         sigemptyset(&act.sa_mask);//初始化需要额外屏蔽的信号
 14         sigaction(2,&act,&oact);//捕捉2号信号
 15         while(1){
 16                 printf("hello world\n");
 17                 usleep(100000);
 18         }
 19         return 0;
 20 }
~         

这里写图片描述

  • pause函数:
    • 1.函数原型:
#include<unistd.h>
int pause(void);
    - 2.函数作用:使调用进程挂起直到有信号递达。
    - 3.返回值说明:
    - <1>.如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回。
    - <2>.若信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回.
    - <3>.如果信号的处理动作是捕捉,则调用了pause之后,pause返回-1,errno设置为 EINTR,。

所以pause只有出错的返回值(exec函数组也一样)。错误码 EINTR表示“被信号中断”。

下面我们用alarm和pause函数来实现sleep函数。
3.实现mysleep函数(1)

 1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<unistd.h>
  4 
  5 void handler()
  6 {}
  7 int mysleep(int timeout)
  8 {
  9         struct sigaction act,oact;
 10         act.sa_handler=handler;
 11         act.sa_flags=0;
 12         sigemptyset(&act.sa_mask);//init;初始化
 13         sigaction(14,&act,&oact);//捕捉14号信号
 14         alarm(timeout);//设置闹钟
 15         pause();//挂起等待
 16         int unsleep=alarm(0);//empty unsleep为-1时表明闹钟取消
 17         sigaction(14,&oact,NULL);//恢复到alarm之前的处理动作。
 18         return unsleep;
 19 }
 20 int main()
 21 {
 22         while(1){
 23                 mysleep(2);
 24                 printf("two seconds passed\n");
 25         }
 26         return 0;
 27 }

这里写图片描述
4.可重入函数的简单介绍
所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。
可重入函数:一个函数只访问自己的局部变量或参数,则称为可重入函数。

如果一个函数符合以下条件之一则是不可重入函数:
<1>.调用了malloc和free,因为malloc也是用全局链表来管理的。
<2>.调用了标准I/O函数。

5.竞态条件与sigsuspend函数
首先,上边的mysleep函数(1)虽然运行无误,但它是有bug的。
在上个程序中,设想时序是这样的:假设不仅仅有一个进程。
1.注册SIGALRM信号的处理函数
2.调用alarm(timeout)设定闹钟。
3.内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有多个。
4.timeout秒之后闹钟超时。内核发送SIGALRM信号给这个进程,属于未决状态。
5.优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函数handler之后再次进入内核。
6.然后返回这个进程的主控制流程,alarm(timeout)返回。调用pause()挂起等待。

那么问题来了:此时SIGALRM信号已经处理完了,还等待什么呢?

出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。

虽然alarm(timeout)紧接着的下⼀行就是pause(),但是无法保证pause()一定会在调用 alarm(timeout)之 后的timeout秒之内被调用。

换句话说,如果在pause函数被调用之前被进程操作系统切换走,由于程序优先级问题,操作系统让它再次回来时此时设定的时间已过,信号已被处理。继续执行pause时就不会收到alarm信号,进程将永远被挂起。
由于异步事件在任何时候都有可能发生(这⾥的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误。这叫做竞态条件 (Race Condition)。
如何解决上述问题呢?

这里有两个方案:

方案一,把程序执行流程改为:

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 函数:
    函数原型:
     #include <signal.h>
       int sigsuspend(const sigset_t *mask);

函数功能:实现解除信号屏蔽与挂起等待信号。
返回值:没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR.
参数解释:
1.调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待。
2.当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

#include<stdio.h>
  2 #include<signal.h>
  3 #include<unistd.h>
  4 
  5 void handler()
  6 {}
  7 int mysleep(int timeout)
  8 {
  9         struct sigaction act,oact;
 10         sigset_t newmask,oldmask,suspmask;
 11         act.sa_handler=handler;
 12         act.sa_flags=0;
 13         sigemptyset(&act.sa_mask);//init;
 14         sigaction(14,&act,&oact);//handler 14 signal
 15         //block SIGALRM
 16         sigemptyset(&newmask);
 17         sigaddset(&newmask,14);
 18         //set sig_set
 19         sigprocmask(SIG_BLOCK,&newmask,&oldmask);
 20         alarm(timeout);
 21 
 22         suspmask=oldmask;
 23         sigdelset(&suspmask,14);
 24         sigsuspend(&suspmask);
 25         int unsleep=alarm(0);//empty
 26         sigaction(14,&oact,NULL);
 27         sigprocmask(SIG_SETMASK,&oldmask,NULL);
 28         return unsleep;
 29 }
 30 int main()
 31 {
 32         while(1){
 33                 mysleep(2);
 34                 printf("two seconds passed\n");
 35         }
 36         return 0;
 37 }

这里写图片描述
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值