编写mysleep

来源:互联网 发布:淘宝更改发货地址 编辑:程序博客网 时间:2024/05/29 18:00

1、内核如何实现信号的捕捉:

  如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号

具体的步骤如下:

1) 用户程序注册了SIGQUIT信号的处理函数sighandler。

 2) 当前正在执行main函数,这时发生中断或异常切换到内核态。

3)在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。

4)内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函

使用不同的堆栈空间,它们之间不存在调用和被调用的关系, 是两个独立的控制流程。

5)sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。

6)如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

2、有关的函数:

(1)sigaction

#include<signal.h>

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

说明:sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则 返回- 1。signo是指定

信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理

动作。act和oact指向sigaction结构体:

struct sigaction{

    void  (*sa_handler)(int);//addr of signal handler,or SIG_ING,or SIG_DFL

    sigset_t  sa_mask;//additional signals to block

    int  sa_flags;//signal options,Figure10.16

  //alternate handler

    void (*sa_sigaction)(int siginfo_t *, void *);

};

对该结构体进行介绍:

      sa_handler:可赋值为SIG_IGN,表示忽略信号;赋值为SIG_DFL,表示默认动作;也可赋值为一个函数指针,表示自

定义的函数捕捉信号,该函数返回值为void,一个int的形参,可得捕捉到的信号编号。

      sa_mask:当屏蔽掉当前信号时,希望有其他的信号同时被屏蔽,该字段说明这些需要额外屏蔽的信号,当信号处

理函数返回时,自动恢复原来的信号屏蔽字。

     sa_flags:不做介绍,以下使用中都置为0。

(2)pause

#include<unistd.h>

int pause(void);

说明:pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终止,pause函数没有机会

返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处

理函数之后pause返回-1,errno设置为 EINTR, 所以pause只有出错的返回值。错误码 EINTR表示“被信号中断”。

3、(1)普通版的mysleep编写



运行结果:



说明:

1)main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
2)调用alarm(seconds)设定闹钟。
3)调用pause等待,内核切换到别的进程运行。
4)seconds秒之后,闹钟超时,内核发SIGALRM给这个进程。
5)从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是handler。
6)切换到用户态执行handler函数,进入handler函数时SIGALRM信号被自动屏蔽,从handler函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
7)pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。
需要注意的是虽然handler函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。此外,mysleep函数的返回值表示“未睡到”的时间,即unslept,当尚未计时到seconds而pause函数先被其他信号处理函数所中断返回,在外界看来就是在sleep期间被其他信号处理函数中断了,则mysleep返回非0值,即unslept。

普通版本的mysleep存在一些问题:


(1)如果调用者已经设置了闹钟,则它被mysleep函数中的第一次alarm调用擦去。

(2)该程序修改了对SIGALRM的配置。如果编写一个函数供其他函数调用,则在该函数被调用时要先保存原配

置,在该函数返回之前再恢复原配置。

(3)因为pause函数使调用进程挂起直到捕捉到一个信号,在调用alarm和pause之间有个竞态条件,在一个繁

的系统,alarm调用pause之前可能超时,并调用了信号处理程序。如果发生这种情况,则在调用pause之后,

如果没有捕捉到其它信号,该调用者会被永远挂起。

(2)规避竞态条件的mysleep

竞态条件:由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误。

解决该问题的方法需要将“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作,而sigsuspend函数刚好有这个功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

看一下sigsuspend函数:

#include<signal.h>

int sigsuspend(const sigset_t *sigmask);

说明:和pause一样,sigsuspend没有返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,error设置为EINTR。调用sigsuspend时,进程的信号屏蔽字有sigmask参数指定,可以通过指定、sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍是屏蔽的。

接下来看代码:



结果为:



如果在调用mysleep函数时SIGALRM信号没有屏蔽 :



1) 调用sigprocmask(SIG_BLOCK, &newmask, &omask);时屏蔽SIGALRM。



2)调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。



3) SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。



4) 调用sigprocmask(SIG_SETMASK, &omask, NULL);时再次解除对SIGALRM的屏蔽。


原创粉丝点击