一、阻塞信号

1.信号其他相关常见概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

2.在内核中的表示


信号在内核中的表示示意图:

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。

3.sigset_t信号集

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

4.信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。

include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);比特位由0变为1
int sigdelset(sigset_t *set, int signo);比特位由1变为0
int sigismember(const sigset_t *set, int signo);
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
  • sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

这四个函数都是成功返回0,出错返回-1。

5.sigprocmask函数


调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。

include <signal.h>
int sigprocmask(int how, const sigset_t set, sigset_t oset);
返回值:若成功则为0,若出错则为-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

6.sigpending函数

include <signal.h>
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。 下面用刚学的几个函数做个实验。程序如下:
1    #include<iostream>
     2    #include<unistd.h>
     3    #include<signal.h>
     4    using namespace std;
     5    
     6    void show_pending(sigset_t* pending)
     7    {
     8        for(size_t sig=1;sig<31;sig++)
     9        {
    10            if(sigismember(pending,sig))
    11            {
    12                cout<<"1";
    13            }
    14            else 
    15            {
    16                cout<<"0";
    17            }
    18        }
    19        cout<<endl;
    20    }
    21    void handler(int sig)
    22    {
    23        cout<<"catch a sig:"<<sig<<endl;
    24    }
    25    int main()
    26    {
    27        signal(2,handler);
    28    
    29        sigset_t pending;
    30        sigset_t block,oblock;
    31    
    32    
    33        sigemptyset(&block);
    34        sigemptyset(&oblock);
    35        
    36        sigaddset(&block,2);
    37      
    38        sigprocmask(SIG_SETMASK,&block,&oblock);
    39    
    40        int count=1;
    41        while(1)
    42        {
    43            sigemptyset(&pending);
    44    
    45            sigpending(&pending);
    46            show_pending(&pending);
    47            sleep(1);
    48            count++;
    49            if(count==20)
    50            {
    51                cout<<"recover sig mask!"<<endl;
    52                sigprocmask(SIG_SETMASK,&oblock,NULL);
    53            }
    54        }
    55    
    56        return 0;
    57    
    58        
    59    
    60    }

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061

二、捕捉信号


1.内核实现信号的捕捉


如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

  • [ ] signal函数:
[yyw@VM-0-9-centos signal捕捉信号函数]$ cat -n test.cpp 
     1    #include<iostream>
     2    #include<signal.h>
     3    #include<unistd.h>
     4    using namespace std;
     5    void hander(int sig)
     6    {
     7        cout<<"catch a sig"<<sig<<endl;
     8    }
     9    int main()
    10    {
    11        signal(2,hander);
    12        while(1)
    13        {
    14            sleep(1);
    15        }
    16        return 0;
    17    }
123456789101112131415161718

  • [ ] sigaction函数:
1    #include<iostream>
     2    
     3    #include<signal.h>
     4    #include<unistd.h>
     5    using namespace std;
     6    void handler(int sig)
     7    {
     8        cout<<"catch a sig"<<sig<<endl;
     9    }
    10    int main()
    11    {
    12    
    13        struct sigaction act,oact;
    14        act.sa_handler=handler;
    15        act.sa_flags=0;
    16        sigemptyset(&act.sa_mask);
    17        sigaction(2,&act,&oact);
    18    
    19        while(1)
    20        {
    21            ;
    22        }
    23        return 0;
    24    }

12345678910111213141516171819202122232425

2.volatile关键字

1    #include<iostream>
     2    #include<unistd.h>
     3    #include<signal.h>
     4    using namespace std;
     5    int quit=0;
     6    
     7    void handler(int sig)
     8    {
     9    
    10        quit=1;
    11        cout<<"catch a sig"<<sig<<endl;
    12    }
    13    int main()
    14    {
    15        signal(2,handler);
    16        while(!quit)
    17        {
    18            sleep(1);
    19            cout<<"end a process"<<endl;
    20        }
    21    }

12345678910111213141516171819202122

1    #include<iostream>
     2    #include<unistd.h>
     3    #include<signal.h>
     4    using namespace std;
     5     volatile int quit=0;
     6    
     7    void handler(int sig)
     8    {
     9    
    10        quit=1;
    11        cout<<"catch a sig"<<sig<<endl;
    12    }
    13    int main()
    14    {
    15        signal(2,handler);
    16        while(!quit)
    17        {
    18            sleep(1);
    19           cout<<"end a process"<<endl;
    20        }
    21       
    22        return 0;
    23    }
1234567891011121314151617181920212223


意义:禁止编译器对该语句做出优化。就比如定义一个全局变量,此时如果优化的话,则这个值会被存到寄存器中,此时你如果对该全局变量进行修改的话,只是对内存中的数据进行修改,寄存器的值是不变的。定义该关键字意义是告诉编译器,想获取我这个值必须要去内存中去取。 禁止将我这个值优化到寄存器中。


总结

以上就是今天要讲的内容,本文仅仅简单介绍了Linux进程间信号后部分的使用,信号的捕捉在用户态和内核态之间的转换,在系统层面上,很多操作是被当成信号处理的。希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。
————————————————
版权声明:本文为CSDN博主「森明帮大于黑虎帮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44918090/article/details/118463673

最后修改:2022 年 04 月 04 日
如果觉得我的文章对你有用,请随意赞赏