第8章 信号(3)_信号的可靠性

3. 信号模型及信号屏蔽

3.1 信号的可靠性

(1)与信号相关的数据结构

 

  ①struct sigpending结构体:。包括一个信号队列和一个信号位图。其中的signal域保存所有待处理信号的集合。因每个信号占一位,该域也可被为信号未决字或信号接收位图)。

  ②blocked字段:进程的信号屏蔽字(或信号屏蔽位图)共31位(代表1-31号信号,0没有意义),每一位代表一个信号,初始为0。当某位被置1时表示屏蔽相应的信号,0时表示不屏蔽信号。如果发生信号时,该相应的标志位己经为1(说明正在处理相同的信号)则新的信号不会被立即处理而会延迟处理(信号未决字相应的标志位被置1)。

  ③sa_mask: 信号屏蔽位图。在处理信号过程中,可以有选择地将若干种其它信号屏掉。注意同一种信号,不论相应标志位是否为1,总是自动屏蔽的。除非sa_flags中的SA_NODEFER

或SA_NOMASK为1)。但要注意的是只有sigaction安装的信号处理函数,sa_mask才会起作用。

  ④action中的sa_handler保存该信号对应的处理函数。早期的Unix在信号发生之后调用信号处理函数之前,会将sa_handler重置默认的信号处理函数(SIG_DFL),这要求每次在信号处理函数时,要自己手动重新注册信号,这也会造成信号的不可靠的。现在的Linux己经在内核层面上保证了信号可靠性。

//早期Unix信号的不可靠性
void sigint_handler(int signo);   //声明函数原型

...
signal(SIGINT, sigint_handler);   //注册信号处理函数
...

void sigint_handler(int signo){
    //在进入sigint_handler函数与再次调用signal之间存在时间窗口
    //在这个“窗口期”中发生的SIGINT不会被捕获到,因为信号处理函
    //数被系统重置SIG_DFL,从而导致进程终止。
    signal(SIGINT, sigint_handler);  //需重新注册信号处理函数!
}

(2)信号屏蔽设置

  ①信号在处理过程中blocked字段相应的标志位被置为1,处理完毕解除屏蔽(置0)。

  ②现在Linux信号在内核层面上都是可靠的。进程在处理信号期间,若发生了同类型的信号不会丢失(相同类型只保留一次,信号未决字相应的标志位被置1),但不会被立即处理,而是延迟处理

  ③若发生不同类型的信号则会被保留并直接处理处理完后再处理原有信号

【编程实验】信号的系统层面可靠性

//signal_rel1.c

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

//演示处理信号过程中,再次发送相同或不同信号时,信号处理的机制。

//注意,由于现在Linux己经保证了信号的可靠性,所以在sig_handler函数中并没有重新注册信号
//处理函数。
void sig_handler(int signo)
{
    static int i1=0, i2=0;
    if(signo == SIGINT){
        printf("process the SIGINT
");
        sleep(5); //睡眠过程中可按ctrl-c或ctrl-z再次发送相同或不同的信号
                  //以测试信号处理机制
        printf("%d pid=%d catch SIGINT
", ++i1, getpid());
        printf("process the SIGINT finished
");
    }

    if(signo == SIGTSTP){
        printf("process the SIGTSTP
");
        sleep(5); //睡眠过程中可按ctrl-c或ctrl-z再次发送相同或不同的信号
                  //以测试信号处理机制
        printf("%d pid=%d catch SIGINT
", ++i2, getpid());
        printf("process the SIGTSTP finished
");   
    }
}

int main(void)
{
    //ctrl-c
    if(signal(SIGINT, sig_handler) == SIG_ERR){
        perror("signal sigint error");
    }

    //ctrl-z
    if(signal(SIGTSTP, sig_handler) == SIG_ERR){
        perror("signal sigtstp error");
    }

    printf("begin running
");
    while(1) pause(); //进程暂停等待信号
    printf("end running
");
    
    return 0;
}
/*输出结果:
 begin running
 ^Cprocess the SIGINT           //连续按两次ctrl-c,第2次会延迟处理
 ^C1 pid=1509 catch SIGINT      //即同一类型信号会延迟处理
 process the SIGINT finished
 process the SIGINT
 2 pid=1509 catch SIGINT
 process the SIGINT finished
 ^Zprocess the SIGTSTP         //连续按两次ctrl-z,第2次会延迟处理
 ^Z1 pid=1509 catch SIGINT
 process the SIGTSTP finished
 process the SIGTSTP
 2 pid=1509 catch SIGINT
 process the SIGTSTP finished  //先按ctrl-c,再按ctrl-z,会先执行ctrl-z,再执行ctrl-c
 ^Cprocess the SIGINT          //即不同类型信号,会立即处理
 ^Zprocess the SIGTSTP
 3 pid=1509 catch SIGINT
 process the SIGTSTP finished
 3 pid=1509 catch SIGINT
 process the SIGINT finished
 ^Zprocess the SIGTSTP         //先按ctrl-z,再按ctrl-c,会先执行ctrl-c,再执行ctrl-z
 ^Cprocess the SIGINT
 4 pid=1509 catch SIGINT
 process the SIGINT finished
 4 pid=1509 catch SIGINT
 process the SIGTSTP finished 

 //可以快速按ctrl-c、ctrl-z、ctrl-c、ctrl-z、ctrl-c、ctrl-z做实验:
 //提示:同类型只被保留一次,所以只会输出前面的4个信号。不同类型会立即处理所以会先执行
 */

(3)Linux信号模型的可靠性

  ①早期的Unix由于信号处理函数执行完,会将处理函数重置为默认的(SIG_DFL),这会造成信号的不可靠现代的Linux信号己经在系统层面保证了信号的可靠性

  ②用户层面也是可靠的,但需要用户自己来维护这种可靠性(将依赖信号而执行的代码放置在信号处理函数中,否则是不可靠的)

【编程实验】用户层面的可靠性

//signal_rel2.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

//信号在用户层面的可靠性

int is_sig_occ = 0; //信号发生的标志

void sig_handler(int signo)
{
    printf("signo occured: %d
", signo);
    is_sig_occ = 1;
}

int main(void)
{
    if(signal(SIGINT, sig_handler) == SIG_ERR){
        perror("signal sigint error");
    }   

    printf("begin running
");
    //如果信号未发生,则暂停。否则执行while后面的语句
    while(is_sig_occ == 0){
        //在判断is_sig_occ与pause之间有个“时间窗口”,
        //如果在这个时间发生信号,则会去执行信号处理函数
        //从而将is_sig_occ改变为1。但这时己晚,这个信号无法
        //触发while后面的语句执行,而是执行完信号处理函数后
        //继续执行后面的pause语句,从而造成信号的不可靠。
        //改进方法:将依赖信号执行的printf("I will running
");
        //代码移到信号处理函数中去执行!
        sleep(5);//为了模拟这个“时间窗口”

        pause();//进程暂停等待信号发生
    }

    printf("I will running
");//信号发生要预期要执行的代码,为了保证这行在
                               //信号发生时一定被执行,应将其放入信号处理
                               //函数中。

    return 0;
}
/*输出结果对比:
 [root@localhost]# bin/signal_rel2
 begin running         //5秒之后按ctrl-c,信号正常触发代码执行
 ^Csigno occured: 2
 I will running
 [root@localhost]# bin/signal_rel2                          
 begin running        //5秒之内按ctrl-c,信号被丢失
 ^Csigno occured: 2
 */
原文地址:https://www.cnblogs.com/5iedu/p/6401981.html