首页 > 代码库 > 信号(四)---一次实验带出的和signal有关的知识

信号(四)---一次实验带出的和signal有关的知识

  之前看APUE上面信号一章的时候,看到APUE上面说早期的signal函数实现让安装的信号处理函数只能使用一次,当第二次再收到该信号的时候,进程将会执行该信号的默认动作,之前安装的信号处理函数将失效。于是我就去做实验验证一下,代码如下:

 1 #include <stdio.h> 2 #include <signal.h> 3  4 static void sigint_act(int); 5  6 int main(int argc,  char *argv[]) 7 { 8         signal(SIGINT, sigint_act); 9 //      sysv_signal(SIGINT, sigint_act);10 11         while (1);12 13         return 0;14 }15 16 17 static void sigint_act(int signo)18 {19         printf("accept SIGINT signal succeed!\n");20     21         sleep(3);22 23         printf("finished!\n");24 }

编译上面的代码后,执行的结果如下图所示:

技术分享

  仔细分析这个结果就知道和APUE上面提到的结论是不符合的;如果按照APUE上面说的,那么在第二次按下ctrl+c键的时候应该去执行SIGINT信号的默认动作,而SIGINT信号的默认动作是终止进程,所以进程应该会终止,但是从实验的结果来看进程并没有终止,这怎么解释?

  接着上网去找资料,看了一篇博客(http://blog.chinaunix.net/uid-24774106-id-4061386.html)后,算是能解释上面的实验结果。博客上面说的原因是现代的signal函数和早期的signal函数并不相同,现代的signal函数安装的信号处理函数可以永久使用。那这就可以解释上面的实验结果中为什么进程没有终止。博客中还提到了sysv_signal函数,这个函数和早期的signal函数的实现是相同的,我用sysv_signal函数做了个实验,发现第二次按下ctrl+c按键时进程确实是立即终止了,如下图所示,证明博客中的内容是正确的。

技术分享

  上面实验结果中虽然解释了为什么进程没有终止,但是为什么会执行两次信号处理函数?博客中的内容也能解释这一原因,早期的signal函数和现代的signal函数还有一点区别就是早期的signal函数在执行某一信号的信号处理函数期间不会屏蔽阻塞该信号,而现代的signal函数会阻塞屏蔽该信号,等待信号处理函数执行完后再去处理该信号。(上面做的sysv_signal函数的实验实际上已经验证了这一点,在执行信号处理函数期间按下了ctrl+c键,进程便立即终止了,说明进程并没有阻塞SIGINT信号,因为如果阻塞了该信号,那么应该会等到信号处理函数执行完毕后,再终止进程);第一次实验的实验结果之所以会执行两遍信号处理函数,是因为按下第二次ctrl+c键的时候正在执行信号处理函数,进程会阻塞该信号,但是仍然收到了该信号,待执行完了信号处理函数后(打印出了finished后)由于进程仍然接收到了信号,所以进程又去执行一遍信号处理函数。于是我又去做了个实验,如下图所示:

技术分享

  当正在执行信号处理函数期间,我又按了6次ctrl+c键,我的想法是会执行6次函数,但是实验结果不遂我愿只执行了两次函数,于是又去网上找资料,找到了一篇博客(http://blog.sina.com.cn/s/blog_7a9cae0101010hth.html),解决了我的这个疑问。

  在博客中提到了信号的“未决状态”和“阻塞状态”,“未决状态”是指进程已经接收到该信号,不管进程处不处理该信号,只是标志着进程已经收到了该信号;“阻塞状态”指该进程在收到该信号后不去响应信号(意思就是不去执行信号的信号处理函数,不管是忽略、默认动作还是安装的函数)。上面的实验中当第二次按下ctrl+c键的时候产生的SIGINT信号处于未决状态,由于在执行信号处理函数期间会阻塞该信号,所以会等到函数执行完毕后再去执行信号处理函数。

  还有一个不可靠信号和可靠信号之分,信号编号在32之前的信号都是不可靠信号,在32之后的都是可靠信号;不可靠信号是指不能排队的信号,信号可能会出现丢弃,可靠信号是指可以排队的信号。按照这个定义,那么SIGINT信号应该是属于不可靠信号(编号小于32),那么为什么上面的实验中好像信号出现了排队的现象,在按下第二次ctrl+c键的时候不应该丢弃的嘛?要解释这个问题,需要去了解信号的生命周期,同时上面的疑问(按6次键)也可以解释了。

  信号的生命周期可以由4个事件来描述:信号产生、信号在进程中注册、信号注销、信号处理函数执行完毕

  • 信号产生:指信号的产生,如按下按键或者利用kill函数产生一个信号;
  • 信号注册:指进程收到该信号,该信号便处于未决状态。在进程的PCB中,有如下图所示的成员来记录未决信号的信息; 信号的注册过程其实就是向成员signal中添加该信号,并在struct sigqueue信息链中添加一个属于该信号的结构体。可靠信号和不可靠信号的信号注册是有一些区别的,上面提到不可靠信号是不可以排队的,意思就是不可靠信号被注册一次后在注销之前是不会再次被注册的;而可靠信号则可以被多次注册。所以对于不可靠信号,在struct sigqueue信息链中一个信号只能有一个结构体,而可靠信号在信息链中可以有多个结构体。

技术分享

  • 信号的注销:在进程转去执行信号处理函数之前,必须要将该信号给注销。注销的实质就是在signal成员中删除该信号,并且在struct sigqueue信息链中将对应的结构体删除。对于不可靠信号来说,signal成员和信息链中都要删除该信号的信息;而对于可靠信号来说,如果该信号注册过多次的话,那么并不会删除signal成员中的信息,只会删除信息链中的一个结构体。
  • 信号处理函数执行完毕:在注销信号后便去执行相应的信号处理函数直至结束。

  有了上面的知识准备,就可以解释上面遗留的两个问题;第一个问题就是为什么SIGINT不可靠信号却好像能够排队,而没有丢弃?这是因为第二次按键是发生在执行信号处理函数期间,而此时之前的SIGINT信号在进程中已经被注销了,那么这时候产生的SIGINT信号就可以被顺利注册。所以其实信号并没有排队。第二个问题为什么按下了6次键却仍然只执行了两次信号处理函数?这个问题就能很好的说明不可靠信号不支持排队,会被丢弃。由于第二次按下ctrl+c键后SIGINT信号已经被注册了,所以接下来产生的5次信号都不能在被注册,即被丢弃,所以只能执行两次信号处理函数。

 

信号(四)---一次实验带出的和signal有关的知识