(莱昂氏unix源代码分析导读-25) signal(下)

                                                 By cszhao1980

下面来看一下信号的处理过程。

首先,进程需要不时的检查自己是否收到signal,此时需要调用issig()函数,该函数会返回

进程此时收到的signalsignal type(如果没有signal,或忽略此signal,则返回0):

3991: issig()

3992: {

3993:    register n;

3994:    register struct proc *p;

3995:

3996:    p = u.u_procp;

3997:    if(n = p->p_sig) {

3998:       if (p->p_flag&STRC) {       /进程处于trace模式,以后再详细解释

3999:          stop();

4000:          if ((n = p->p_sig) == 0)

4001:             return(0);

4002:       }

4003:       if((u.u_signal[n]&1) == 0)  /偶数表示,不可忽略此信号,故返回signal type

4004:           return(n);

4005:    }

4006:    return(0);

4007: }

 

当确定有收到信号后,会调用psig()进行处理:

(1)         如果有指定信号处理程序地址,则执行之;

(2)         否则,结束进程。

 

听起来很简单,但令人深感意外的是,pisg()程序比想象中复杂的多。

这是“信号处理程序”身处user空间,而目前,进程在kernel态。

 

在讲解之前,首先补充一点知识:psig()函数只被中断/陷入处理程序调用,

因此,如果调用到了该函数,证明此时肯定发生了一次中断/陷入。

 

4055: n = u.u_ar0[R6] - 4;

4057: suword(n+2, u.u_ar0[RPS]);

4058: suword(n, u.u_ar0[R7]

 

这三条语句直接操纵user stack,在user stack中插入“中断/陷入”时的user PSpc

 (莱昂氏unix源代码分析导读-25) signal(下)_第1张图片


4059: u.u_ar0[R6] = n;                        /user态的r6(sp),使其指向新栈顶

4060: u.u_ar0[RPS] =& ~TBIT;        /修改user态的PS

4061: u.u_ar0[R7] = p;                     /修改userr7pc),使其指向“信号处理程序”

4062: return;                                      /return回 “中断/陷入”程序入口call中,然后rtt

                                                          /即跳回到user态,而前三条语句造成的register的改变

                                                         /将生效,即会马上执行“信号处理程序”

 

而信号处理程序执行完毕后,return时,会跳回到栈顶执行的地址——而这个地址已被我们更新

为“中断/陷入”前的地址,以恢复被中断的程序。巧妙的设计!

 

刚才的说明忽略了第4056行:“4056 grow(n)”——它的存在是为了处理一种概率很低的情形,即user

 stack空间已耗尽,无法容纳要插入的PSpc的情景。

 

grow()函数会检查user stack segment,如果空间不足,就会扩展stack segment空间。理解grow()的关键是

要了解stack segment在进程逻辑地址空间的分配——它总是会被放置在user态逻辑地址空间的最后,而

栈底就是“最大地址”,即0177777。理解了这一点,再结合莱昂的注释,相信此函数不难理解。

 

【思考题】:4054行将该信号的处理程序清0,会有什么后果?

                        如果user程序希望此种信号一直由某个程序处理,应该怎么办?

 

显然,进行信号处理典型的写法为:if(issig())  psig(),如:

(1)         trap2693)函数,执行完陷入例程后,会检查信号,并处理;

(2)         clock3725),时间片处理时会检查信号,并处理。

 

但是,sleep2066)的处理方法不同:

2072:   if(pri >= 0) {

2073:      if(issig())

2074:          goto psig;

          ……

2084:          swtch();

2085:      if(issig())

2086:          goto psig;

             …..

2105: psig:

2106:     aretu(u.u_qsav);

          (隐含的return语句)

 

当以低优先级(大于0priority)调用sleep时,在睡眠前后,都会检查信号。一旦有信号发生,

会执行aretu(u.u_qsav)——会以u_qsav[ ]恢复spr5,但不会进行进程switch——然后执行隐含的return语句。

 

return到哪里去呢?

 

首先,虽然调用sleep的地方很多,但最终必然是通过sys call进行调用的,才会以低优先级调用sleep()

trap(2693)程序中:2771 trap1(callp->call),而trap1程序如下:

 

2841: trap1(f)

2842: int (*f)();

2843: {

2844:

2845:    u.u_intflg = 1;

2846:    savu(u.u_qsav);

2847:    (*f)();

2848:     u.u_intflg = 0;

2849: }

 

呵呵,会returntrap1的调用者——trap中,而我们刚刚看到的,trap函数随后会检查信号,并加以处理。

由于sleep在调用switch前也会检查信号,所以,如果此时进程已经收到信号了,进程不会睡眠,

而是直接返回trap,进行信号处理。

 

上面描述的是用户指定了信号处理程序的情形,否则,psig()会调用core()core dump,然后调用exit()退出。

考察进程退出时的动作是个很有趣的话题,让我们看一下exit3219)函数:

 

13224:清理STRC标志;

23225~3226:忽略所有信号;

3)关于io等等操作(略过);

43243:将状态(p_stat)设置为SZOMB(僵尸态);

5Loop进程表,找到其父进程
   

        3249   wakeup(p);  /唤醒“父进程”,但父进程何时sleep(进程表项地址,  )的?

 

6Loop进程表,找到其所有子进程

     1)将子进程的父进程id设置为#1进程;

     2)如果进程处于SSTOP状态,则setrun它——事关trace,我们将在后面介绍。

 

显然,调用exit之后,进程没有彻底消亡——它变成了僵尸进程,并仍然占据进程表项。那么,是

谁真正清理了进程表呢?是wait3270)函数。wait()函数实现第7sys call wait的功能,它会:

 

(1)         loop进程表,找到所有的状态为SZOMB的子进程,进行清理工作——主要是清理进程表项;

(2)         处理状态为SSTOP的子进程——事关trace,我们下节再介绍;

(3)         如果无SZOMB状态的子进程,则调用“3314sleep(u.u_procp, PWAIT)”睡眠;

 

可见,以u.u_procp(进程表项地址)睡眠原因的进程,进入睡眠是为了处理子进程exit和子进程

trace——即会被此两种情况唤醒。

 

(4)         如果根本没有子进程,则出错return

 

【思考题】:如果某父进程早于子进程结束,那么其子进程结束后会否因无人清理而一直占据proc表项?

 

博客地址:http://blog.csdn.net/cszhao1980

博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html



你可能感兴趣的:((莱昂氏unix源代码分析导读-25) signal(下))