Linux系统编程--信号篇

Posted 蚍蜉撼树谈何易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux系统编程--信号篇相关的知识,希望对你有一定的参考价值。

初识信号

生活上的信号

你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”(操作系统可以根据你发出的信号你选择不同的动作)
当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。
在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏) --对应的是信号处理的三个分类。
快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

技术层面上的信号

信号就是程序要执行某件事情之前发送的信号,通知进程要做什么事情,是一个软件中断信号是进程之间事件异步通知的一种方式
举个例子:就比如你在一个死循环中按下ctrl+c;,此时操作系统会接收到一个2号信号,发送给目标前台进程,终止该程序。
注:ctrl+c 只可以终止掉前台进程,不可以终止后台进程。
软中断是软件实现的中断,也就是程序运行时其他程序对它的中断;而硬中断是硬件实现的中断,是程序运行时设备对它的中断。

信号的分类

使用kill -l 命令查看信号
在这里插入图片描述
在linux中,共有62个信号。
分类:
非可靠信号(非实时信号):
1-31号信号,为不可靠信号。其如何传递多个相同的信号的话,有可能会造成信号的丢失。
深得来说:就是底层采用位图的方式来保存接收的信号,当收到多个相同信号时,操作系统不会将其进行额外处理,就是存储信号的位图中假如标识当前信号的比特位已经被置‘1’了,则再接收到该信号,则保持原来位图的样子即可。
可靠信号(实时信号)
34-64号信号被称为可靠信号,信号绝对不会丢失(有一个专门的结构体去保存该信号,底层通过链表形式将这些信号依次连接起来,遇见一个就加一个,哪怕是一样的)。

信号的常见处理方式

  1. 忽略此信号。
  2. 执行该信号的默认处理动作。
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号
    在这里插入图片描述

在这里插入图片描述

了解信号的几大步骤

在这里插入图片描述

信号的产生

1.通过按键产生。
比如ctrl+c : 2号信号SIGINT
ctrl+\\ :3号信号 SIGQUIT 相比较2号信号来说,这个信号默认会产生core dump(核心转储) 文件
2.调用系统函数向进程发信号

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1

测试kill

 1 #include<iostream>                                                                                              
  2 #include<unistd.h>                                                                                              
  3 #include<signal.h>                                                                                              
  4 using namespace std;                                                                                              
  5 //1.首先自定义信号的处理方式                                                                                              
  6 void hander(int arg)                                                                                              
  7 {                                                                                              
  8     cout<<arg<<"号信号在此处被处理"<<endl;                                                                                              
  9 }                                                                                              
 10 int main()                                                                                              
 11 {                                                                                              
 12     signal(2,hander);                                                                                              
 13     //测试kill                                                                                              
 14     while(1)                                                                                              
 15     {                                                                                              
 16         kill(getpid(),2);                                                                                              
 17         sleep(1);                                                                                              
 18     }                                                                                                                                                                                 
 19     return 0;                                                                                              
 20 } 

在这里插入图片描述
raise(int signo)

  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 using namespace std;
  5 //1.首先自定义信号的处理方式
  6 void hander(int arg)
  7 {
  8     cout<<arg<<"号信号在此处被处理"<<endl;
  9 }
 10 int main()
 11 {
 12     signal(2,hander);
 13     //测试kill
 14     while(1)
 15     {
 16     //    kill(getpid(),2);
 17         raise(2);                                                                                                                                                                     
 18         sleep(1);
 19     }
 20     return 0;
 21 }

在这里插入图片描述
abort函数使当前进程接收到6号信号而异常终止
相比较之前的2号信号,6号必然要生效。就算你更改了函数的默认处理动作。

  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 #include<stdlib.h>
  5 using namespace std;
  6 //1.首先自定义信号的处理方式
  7 void hander(int arg)
  8 {
  9     cout<<arg<<"号信号在此处被处理"<<endl;
 10 }
 11 int main()
 12 {
 13     signal(6,hander);
 14     //测试abort
 15         abort();                                                                                                                                                                      
 16     while(1)
 17     {
 18     //    kill(getpid(),2);
 19        // raise(2);
 20        // abort();
 21         sleep(1);
 22     }
 23     return 0;
 24 }

在这里插入图片描述

注:9号信号是不可以被屏蔽掉的。

 1 #include<iostream>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 #include<stdlib.h>
  5 using namespace std;
  6 //1.首先自定义信号的处理方式
  7 void hander(int arg)
  8 {
  9     cout<<arg<<"号信号在此处被处理"<<endl;
 10 }
 11 int main()
 12 {
 13     signal(9,hander);
 14     //测试kill
 15         abort();
 16     while(1)
 17     {
 18     //    kill(getpid(),2);
 19         raise(9);                                                                                                                                                                     
 20        // abort();
 21         sleep(1);
 22     }
 23     return 0;
 24 }

3.由软件条件产生的信号。

比如在管道中,如果将读端关闭的话,此时写端如果要再想写的话就会收到SIGPIPE 13 号信号。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号14号信号, 该信号的默认处理动
作是终止当前进程
 #include<iostream>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 #include<stdlib.h>
  5 using namespace std;
  6 //1.首先自定义信号的处理方式
  7 void hander(int arg)
  8 {
  9     cout<<arg<<"号信号在此处被处理"<<endl;
 10 }
 11 int main()
 12 {
 13     alarm(10);                                                                                                                                                                        
 14    // signal(9,hander);
 15     //测试kill
 16     while(1)
 17     {
 18     //    kill(getpid(),2);
 19         //raise(9);
 20         abort();
 21 
 22         sleep(1);
 23     }
 24     return 0;
 25 }
~

在这里插入图片描述
4.硬件错误产生信号。
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 **为SIGFPE信号(8号信号)发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号(11号信号)**发送给进程。

注:所有的信号,都必须经过操作系统的手发出。
原因:因为信号会影响进程,进程被操作系统所管理。

信号的保存

在这里插入图片描述

信号的处理

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

在这里插入图片描述

修改/操作信号

//下面这些函数是设置的, 真正的写入在sigprocmask()

#include <signal.h>
int sigemptyset(sigset_t *set); //将函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号
int sigfillset(sigset_t *set);//函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号
int sigaddset (sigset_t *set, int signo);//加上某个信号
int sigdelset(sigset_t *set, int signo);//删除某个信号
int sigismember(const sigset_t *set, int signo);//判定一个信号是否在当前信号集中

了解sigprocmask()函数
本质上:对信号屏蔽字(block位图)操作

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

how的取值:
在这里插入图片描述

//获取当前pending位图的值
sigpending(sigset_t set);
在这里插入图片描述

1 #include<stdio.h>                                                                                                                                                                     
  2 #include<signal.h>
  3 #include<unistd.h>
  4 void show(sigset_t pending)
  5 {
  6   int i=0;
  7   for(i=0;i<=31;++i)
  8   {
  9       if(sigismember(&pending,i)==1)
 10       {
 11           printf("1");
 12       }
 13       else
 14       {
 15           printf("0");
 16       }
 17   }
 18   printf("\\n");
 19 }
 20 void hander(int arg)
 21 {
 22   printf("i get signal %d\\n",arg);
 23 }
 24 int main()
 25 {
 26     signal(2,hander);
 27     sigset_t newpending,oldpending;
 28     sigset_t pending;
 29     //将声明的newpending,oldpending 置0
 30     sigemptyset(&newpending);
 31     sigemptyset(&oldpending);
 32     //将2号信号写入进去
 33     sigaddset(&newpending,2);
 34     //将设置好的newpending写入到block()中,oldpending 中保存设置之前的表
 35     sigprocmask(SIG_SETMASK,&newpending,&oldpending);
 36     int count=0;
 37     while(1)
 38     {
 39        sigemptyset(&pending);
 40        sigpending(&pending);
 41        show(pending);
 42        sleep(1);
 43        count++;
 44        //在20s后取消阻塞
 45        if(count==20)
 46        {
 47            printf("%s","将block位图中对应的2号信号未置0,取消屏蔽\\n");
 48            sigdelset(&newpending,2);
 49            sigprocmask(SIG_SETMASK,&newpending,NULL);
 50        }
 51     }
 52 }                   

在这里插入图片描述

信号的捕捉

在这里插入图片描述
函数1 :signal(int signo ,函数);
在这里插入图片描述

 1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<unistd.h>
  4 void hander(int arg)
  5 {
  6     printf("i got a signal%d\\n",arg);
  7 }
  8 int main()
  9 {
 10     signal(2,hander);
 11     while(1)
 12     {
 13         sleep(1);
 14     }
 15     return 0;                                                                                                                                                                         
 16 
 17 }

在这里插入图片描述

函数2:

在这里插入图片描述

1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<unistd.h>
  4 void hander(int arg)
  5 {
  6     printf("i got a signal%d\\n",arg);
  7 }
  8 int main()
  9 {
 10     //signal(2,hander);
 11     //while(1)
 12     //{
 13     //    sleep(1);
 14     //}
 15     struct sigaction t1,t2;
 16     t1.sa_handler=hander;
 17     t1.sa_flags=0;
 18     sigemptyset(&t1.sa_mask);
 19     sigaction(2,&t1,&t2);
 20     while(1)
 21     {
 22         sleep(1);                                                                                                                                                                     
 23     }
 24     return 0;
 25 
 26 }
~

在这里插入图片描述

信号的注销

在这里插入图片描述

volatile关键字的作用

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

以上是关于Linux系统编程--信号篇的主要内容,如果未能解决你的问题,请参考以下文章

Linux 编程之信号篇:异常监控必知必会

Linux 编程之信号篇:异常监控必知必会

Linux 编程之进程篇:task_struct进程创建和退出

Linux 编程之进程篇:task_struct进程创建和退出

Linux 编程之进程篇:task_struct进程创建和退出

Linux系统编程Linux信号列表