之前我们知道,操作系统会在合适的时候对接收到的信号做出相应的处理。
那么现在我们知道这个合适的时候就是:进程从内核态返回到用户态,会检查信号,并作出相应的处理。
下面我们来详细的说下这到底是怎么回事?
1、当我们在执行我们所写的一个程序的时候,可能会因为中断、异常或者系统调用从而进入内核。
2、进入内核,内核会处理这些问题,处理完以后内核会返回到用户态,然而在返回之前,内核做的最后一件事情就是处理当前进程中可以抵达的信号。
3、若处理动作是用户自定义的处理函数,则会回到用户态执行该信号处理函数(不是一开始产生异常情况就进入内核的地方)也就是主控制流程。
4、信号处理函数返回时执行特殊的系统调用sigreturn再次进入内核。
5、返回用户模式从主控制流程中上次被中断的地方继续向下执行。
一、内核如何实现信号的捕捉?
如果信号的处理动作是用户自定义的函数,在信号抵达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂。举例:用户程序注册了SIGQUIT信号的处理函数sighandler,当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查有信号SIGQUIT抵达。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,**sjghandler函数和main函数使用不同的堆栈空间,它们之间的关系不存在调用和被调用的关系,是两个独立的控制流程。**sjghandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。如果没有新的信号要抵达,这次再返回用户态就是恢复main函数的上下文数据继续执行了。
sigaction函数
读取和修改与指定信号相关联的处理动作,成功返回0,出错返回-1
signore:指定信号的编号
act指针非空:根据act 修改该信号的处理动作
oact指针非空:通过oact传出该信号原来的处理动作
act和oact指向sigaction结构体,将sa_handler赋值为常数SIGINT传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行默认动作,赋值为一个函数指针表示用自定义函数捕捉信号或者说向内核注册了一个信号处理函数。
pause函数
pause函数使调用进程挂起直到有信号抵达(在这里的挂起的含义:挂起相当于T状态;就相当于从运行队列转到暂停队列)pause只有出错的返回值
如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后,pause返回-1,errno设置为EINTR.错误码ERRTR表示“被信号中断”。
二、下面我们将用alarm和pause实现mysleep
main函数调用mysleep函数,mysleep函数调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
2、调用alarm(nsecs)设定闹钟。
3、调用pause等待,内核切换到别的进程运行。
4、nsecs秒之后,闹钟超时,内核发SIGALRM信号给这个进程
5、从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm.
6切换到用户态执行sig_alrm函数,混入sig_alrm函数时SIGALRM信号被自动屏蔽,从sia_alrm函数返回时SIGALRM信号自动解除屏蔽,然后自动的执行系统调用sigreturn再次进入内核,再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用mysleep函数)。
7、pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。
代码实现如下:
1 #include <stdio.h>
2 #include <signal.h>
3 #include <unistd.h>
4
5 void sig_alrm(int signo)
6 {
7 //这里的信号处理函数不许要干啥事情
8 //注册它是为了捕捉信号,从而pause返回
9 }
10 unsigned int mysleep(unsigned int nsecs)
11 {
12 struct sigaction new,old;
13 unsigned int unslept=0;
14 new.sa_handler=sig_alrm;
15 sigemptyset(&new.sa_mask);
16 new.sa_flags=0;
17 //注册信号处理函数
18 sigaction(SIGALRM,&new,&old);
19 //设置闹钟
20 alarm(nsecs);
21 pause();
22 //清空闹钟
23 unslept=alarm(0);
24 //恢复默认信号处理动作
25 sigaction(SIGALRM,&old,NULL);
26 //返回0说明休眠时间够
27 //非0说明没到规定时间
28 return unslept;
29 }
30 int main()
31 {
32 while(1)
33 {
34 mysleep(3);
35 printf("3 seconds passed\n");
36 }
37 return 0;
38 }
39
~
注意:信号处理函数sig_alrm什么都没干,为什么还要注册它作为SIGALRM的处理函数?不注册信号处理函数可以吗?(SIGALRM会终止进程?)
pause函数只有遇到用户自定义的信号捕捉函数时才会返回。所以为了让sig_alrm函数返回才定义的catch()函数。
为什么在my_sleep函数返回前要恢复SIGALRM信号原来的sigaction?
不恢复也可以,但是我们要遵守一定的规则,即不要改变函数原先的行为,所以就要恢复它本来的行为。这样也方便我们再次调用时把握它的处理方式。
my_sleep函数的返回值表示什么含义?什么情况下返回非0值?
返回0表示取消闹钟,已经唤醒成功。如果在闹钟还没响时就取消闹钟,返回值表示还有多久闹钟才会响。