捕捉信号与sleep模拟

来源:互联网 发布:英国牛津大学知乎 编辑:程序博客网 时间:2024/06/09 15:16

一、捕捉信号

1、对信号的三种处理方式:

(1)忽略此信号:大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的,原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是示定义的。
(2)直接执行进程对于该信号的默认动作 :对大多数信号的系统默认动作是终止该进程。
(3)捕捉信号:执行自定义动作(使用signal函数),为了做到这一点要通知内核在某种信号发生时,调用一个用户函数handler。在用户函数中,可执行用户希望对这种事件进行的处理。注意,不能捕捉SIGKILL和SIGSTOP信号。

2、捕捉信号用到的函数

(1)SIGALRM 信号:

时钟定时信号, 计算的是实际的时间或时钟时间, alarm函数使用该信号。

(2)alarm函数:

include <unistd.h>unsigned int alarm(unsigned int seconds);

alarm也称为闹钟函数。它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程。
返回值是0或者是以前设定的闹钟时间还余下 的秒数。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

(3)pause函数:

include <unistd.h> int pause(void); 

pause函数使调用进程挂起直到有信号递达。pause只有出错的返回值。errno设置为EINTR表示“被信号中断”。
如果信号的处理动作是终止进程,则进程终止, pause函数没有机会返回;
如果信号的处理动作是忽略,则进程继续处于挂起状态, pause不返回;
如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回- 1;

(4)sigaction函数:

include <signal.h>int sigaction(int signo, const struct sigaction *act, structsigaction *oact);

sigaction函数:成功返回0,失败返回-1
signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。

(5)sigaction的结构体:

这里写图片描述

sa_handler:赋值为常数SIG_IGN传给sigaction表示忽略信号;赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号。 向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
sa_mask:进程的信号屏蔽字。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
sa_flags:sa_flags字段包含一些选项,本文代码把sa_flags设为0。
sa_sigaction是实时信号的处理函数。

3、系统捕捉信号的过程:

这里写图片描述
(1)当一个正在运行的进程收到了中断,异常,或系统调用时,会从用户 态切换至内核态;
(2)当内核处理完异常或中断时不会立即返回用户态,在回到用户态之前系统会检查要返回进程PCB中的signal位图信息。如果当前进程的pending表中有还未递达的信号(pending表中有标志是1),内核会将悬挂的信号进行处理:
(3)如果悬挂信号的处理方式是执行自定义动作,那么此时会从内核态切换至用户态执行用户自定义的handler函数;
(4)待系统处理完信号自定义的句柄函数时,系统会执行特殊的系统调用sigreturn再次回到内核态;
(5)处理完sigreturn之后再次从内核态切换至用户态执行从主控流程main函数中上次被中断的地方继续向下运行……

二、模拟mysleep

普通版本的mysleep

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

函数详解:
1、main函数调用my_sleep函数,后者调用sigaction注册了SIGALRM信号的处理函数myhandler。
2、调用alarm(timeout)设定闹钟。
3、调用pause等待,内核切换到别的进程运行。
4、timeout秒之后,闹钟超时,内核发SIGALRM给这个进程。
5、从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是myhandler。
6、切换到用户态执行handler函数,进入handler函数时SIGALRM信号被自动屏蔽, 从myhandler函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程。
7、pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。

程序运行过程中遇到一个问题,虽然程序按照流程运行完成,但是并没有结束,直到人为的按ctrl C才结束运行,那么为什么会这样呢?
因为程序的时序,优先级等问题导致程序没有按预期运行,有可能在设置闹钟之后由于某种原因程序被切出去了,等时钟到了固定时间后程序仍没切回来,此时会将信号递达,等程序切回来时pause有可能再也等不到信号来临导致程序一直被挂起。

根本原因就是系统运行代码时并不会按照我们的思路走,虽然alarm(timeout)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(timeout)之后的timeout秒之内被调用。由于异步事件在任何时候都有可能发生,如果写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件。

避免竞态条件的mysleep

程序改善:用sigsuspend代替pause,sigsuspend函数既包含了pause的挂起等待功能,同时又解决了竞态条件的问题。

include <signal.h>    int sigsuspend(const sigset_t *sigmask);

在对时序要求严格的场合下都应该调用sigsuspend而不是pause。
如果在调用my_sleep函数时SIGALRM信号没有屏蔽:
1)调用sigprocmask(SIG_BLOCK,&newmask, &oldmask)时,屏蔽SIGALRM。
2)调用sigsuspend(&suspmask)时,解除对SIGALRM的屏蔽,然后挂起等待。
3)SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
4)调用sigprocmask(SIG_SETMASK, &oldmask, NULL)时,再次解除对SIGALRM的屏蔽。
这里写图片描述
这里写图片描述

pause与sigsuspend:

(1)sigsuspend函数接受一个信号集指针,将信号屏蔽字设置为信号集中的值,在进程接受到一个信号之前,进程会挂起,当捕捉一个信
号,首先执行信号处理程序,然后从sigsuspend返回,最后将信号屏蔽字恢复为调用sigsuspend之前的值。
(2)pause函数使调用进程挂起直到捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回
sigsuspend函数是pause函数的增强版。当sigsuspend函数的参数信号集为空信号集时,sigsuspend函数是和pause函数是一样的,可以接受任何信号的中断。 但是sigsuspend函数可以屏蔽信号,接受指定的信号中断。

sigsuspend函数=pause函数+指定屏蔽信号

原创粉丝点击