Linux从青铜到王者第十二篇:Linux进程间信号第二篇

Posted 森明帮大于黑虎帮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux从青铜到王者第十二篇:Linux进程间信号第二篇相关的知识,希望对你有一定的参考价值。

系列文章目录



前言


一、阻塞信号

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	}

二、捕捉信号

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	}

  • sigactio函数:

     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	}

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	}

     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	}


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


总结

以上就是今天要讲的内容,本文仅仅简单介绍了Linux进程间信号后部分的使用,信号的捕捉在用户态和内核态之间的转换,在系统层面上,很多操作是被当成信号处理的。希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。

以上是关于Linux从青铜到王者第十二篇:Linux进程间信号第二篇的主要内容,如果未能解决你的问题,请参考以下文章

Linux从青铜到王者第十七篇:Linux网络基础第二篇之UDP协议

Linux从青铜到王者第二十二篇:Linux高级IO

Lua从青铜到王者基础篇第十二篇:Lua错误处理

C++从青铜到王者第十二篇:深入理解默认成员函数之日期类的实现

Linux从青铜到王者第十六篇:Linux网络基础第二篇之HTTP协议

Linux从青铜到王者第十八篇:Linux网络基础第二篇之TCP协议