0


linux信号| 学习信号三步走 | 学习信号需要打通哪些知识脉络?

    **前言:** 本节内容主要讲解linux下信号的预备知识以及信号的概念, 信号部分我们将会分为几个阶段进行讲解:信号的概念, 信号的产生, 信号的保存。本节主要讲解信号

** ps:本节内容适合学习了进程相关概念的友友们进行观看哦**

什么是信号

     在我们生活当中, 有许多都是信号的模式。 ——比如**我今天点了一个外卖, 我点了之后就在宿舍打游戏了。 之后, 外卖小哥到了, 给我打了一个电话, 敲我的门。 那么这种情况, 就叫做信号产生了**。 **当我收到这个信号的时候, 我可能有事情, 那么我即便收到了信号, 也不会立即处理, 而是把事情忙完之后, 再进行处理。 也就意味着, 信号暂时得被我记录下来。 ——我们取外卖的动作, 就叫做信号处理; 外卖小哥给我们打电话叫做信号的产生; 而我们可能在做更重要的事情, 比如打游戏, 我们得等一等, 所以我们会把信号产生的这个事情记录下来, 在合适的时候进行处理, 这个就叫做信号保存。****整个流程下来, 就叫做信号产生到信号处理的生命周期!**

常见的信号有哪些

    我们的上课铃声, 我们的门铃, 取快递,爸爸妈妈都脸色不太好, 古代的冲锋号, 狼烟等等都叫做信号。 

进程如何认识信号

    虽然计算机内部有信号, 但是我们的进程是怎么认识信号的呢?
  • ** 对于进程来说, 进程一定能够识别并处理信号的能力**
  • ** 并且, 进程即便没有收到信号, 也能知道那些信号该怎么处理。——这个信号处理能力是必须是属于进程内置功能的一部分。**
  • ** 当进程真的收到了一个具体的信号的时候, 进程可能并不会立即处理信号, 因为进程可能在做更重要的事。 **
  • ** 进程当信号产生, 到信号被处理, 这个中间一定有着时间窗口, 所以进程必须有临时保存信号的能力。**
    所以, 综上, 我们就知道, 什么叫做认识信号?——认识信号就是能够先**识别信号**, 然后**知道信号的处理方法**。 就比如我们人, 我们知道了红灯, 门铃这些信号该干什么, 是因为我们记住了这些信号的识别方法和处理方法。

前台启动与后台启动

我们运行下面这个程序

#include<iostream>
using namespace std;
#include<unistd.h>


int main()
{
  while (true)
  {
    cout << "i am a crazy process" << endl;
  }                                                                                                                                                                                  return 0;
}

然后运行结果如下:

    我们除了进程再运行, 其他看不出什么问题。 但是呢, 上面的进程一直在跑, 我们按什么都没有用, 所以这个时候我们ctrl + c, 就可以让进程退出。 
    为了更好的测试我们这里让程序睡眠上一秒钟:
#include<iostream>
using namespace std;
#include<unistd.h>


int main()
{
  while (true)
  {
    cout << "i am a crazy process" << endl;
    sleep(1);
  }                                                                                                                                                                                  return 0;
}

这个程序运行起来了, 但是问题是我们再输入什么都没有用了。 这是为什么?这是因为我们启动的进程是一个前台进程, **什么是前台进程, 这种我们的进程启动后, 我们的bash进程就不再接收任何指令了, 这种进程, 就叫做前台进程。 **

现在, 我们在进程运行的时候输入一个&, 这个的意思是后台进程。

    因为进程的作用就是打印数据, 所以他还会在命令行上打印数据, 并且我们可以在命令行上输入指令, bash会接收这些指令。
    但是问题是我们使用ctrl + c杀不掉这个进程了, 如下图:

这是因为在Linux终端中,** 一次登录中, 一个终端, 一般会配上一个bash。 每一个登录, 只允许一个进程是前台进程, 可以允许多个后台进程。 像这种后台进程只能使用kill -9 PID**杀掉这个进程。

    现在我们要说的是, **为什么我们运行一个myprocess后, 再输入ls等指令就没用了呢?**——这是因为我们**./myprocess默认是前台进程启动**。 而**我们说过, 每一个登录, 其实只允许一个进程是前台进程**。 要知道, 我们的**bash也是进程, 而且也是前台进程**!那么,** 当我们的./myprocess后, 我们的./myprocess就变成了前台进程, 而bash变成了后台进程, 那么再在bash里面输入就没有作用了**。 所以, **前台和后台的本质区别就是——谁来获取键盘输入**!当我们**运行myprocess之后, 获取键盘输入的就是我们的myprocess**了, 所以**再输入指令没有反应, 因为myprocess不会对指令做出反应!因为bash收不到指令**!
    再回过头来看我们的这个问题——**为什么我们myprocess &, 让进程后台运行之后ctrl + c后杀不掉这个./myprocess进程呢?** ——这是因为**当我们的后台运行myprocess, 接收ctrl + c的进程是我们的bash进程, myprocess接收不到命令, 也就不能杀掉这个进程。 **

** 那么问题又来了, 我们的bash进程ctrl + c后, 为什么不会被干掉呢? 这是因为我们的bash进程内部对于ctrl +c做了特殊的处理。 **

    那么, 当我们myprocess后台运行的时候, 随让我们同样可以看到打印, 但是请问这个时候我们可以继续输入指令吗? 

    答案是可以的, 就像上图, 当我们后台运行myprocess进程, 这个时候虽然会继续向显示器上面打印数据, 但是当我们输入指令,** 即便这个指令会被数据断开, 但是bash命令会忽略这些后台打印的数据。 **

     也就是说, **表面上看我们的指令是被扰乱的, 但是其实这些指令并没有被扰乱, 这个指令还是完整的输给了前台进程**。 我们**为什么看到的是乱的仅仅是因为我们在进行输入的时候, 字符在显示器上面被回显出来了, 我们在输出上面互相干扰了。** ——我们**输入有输入缓冲区, 输出有输出缓冲区**。 **输入缓冲区也就是bash的输入缓冲区, 我们使用ls命令, 本质上是ls输入到bash的输入缓冲区当中。 并且, 不光光是给输入缓冲区了, 同时也给了输出缓冲区显示器上面给我们回显了。 **
   每当我们登录上一个终端, 每一个终端都会配上一个bash。 一般情况下,bash是前台进程, 他会获取终端的键盘输入, 但是当我们执行一个进程的时候, 它默认会变成一个前台进程, bash会变成一个后台。 整个的在我们的进程一次登录当中, 只允许一个进程是前台, 多个进程是后台。 

** 而之所以我们的ctrl + c能够杀掉前台进程, 是为什么——ctrl + c是我们的键盘输入, 是被前台进程收到的。 ——所以在进程ctrl + c的时候, 本质是被前台进程解释成为收到了信号,这个信号是2号信号。 **

信号的种类

**    kill -l可以查看我们当前的linux系统一共支持多少信号。 ——注意, 没有0号信号, 没有32, 33好信号。 一共62个信号。 其中, 1 ~31被称为普通信号,后面的信号被称为实时信号。 什么是实时信号——我们说过, 一个信号产生了, 那么我们可以不去立即处理, 这种叫做普通信号, 而一旦信号产生了, 我们必须尽快处理, 这种信号被称为实时信号。  **
    我们的ctrl + c 其实就是出发了二号信号——SIGINT.

信号的处理方式

    当我们收到一个信号的时候, 我们会在合适的时候处理这个信号。 信号的处理方式是什么——
  • ** 就比如当我们路过红路灯的时候, 遇到绿灯, 我们会走, 红灯我们会停下来——这就是我们处理信号的默认动作; **
  • ** 有一些人看到红灯之后不管红灯, 直接闯红灯——这就叫忽略;**
  • ** 红灯亮的时候, 有些人天生就是显现包, 红灯一亮, 他就唱歌, 他就跳舞等等, 这些动作是他们自己按照自己的想法来的——这就叫做自定义动作。 这种自定义动作通常叫做信号的捕捉。 **
    我们上面说的收到二号信号的默认动作, 就是终止自己。 

如何捕捉信号呢?就是使用下面的函数:

    这里的signal函数, 谁要调用它, 就要传送两个参数, 第一个参数就是上面的signal标号。 而后面的hander就是谁调用这个函数, 执行这个信号的自定义捕捉动作!!! 未来如果我们想收到一个信号, 并在收到这个信号时, 让他自定义去执行动作, 就把要执行的动作和信号通过这个函数进行绑定!——即这个函数, 是用来修改特性进程对信号的处理工作的。 
    第一个参数是int类型, 传送的是信号的编号。 确定的是哪一个信号。 所以, 我们就要传送这个信号的编号。 下面是宏定义:

    然后, 定义下面的代码,并执行:

    然后我们就会发现, **原本的ctrl + c会直接退出, 但是现在不退出了, 而是执行我们的自定义的动作**。 那么我们就验证了, 我们的ctrl + c其实就是要发送信号。 并且**默认动作和自定义动作只会执行一个**。 

    **注:想要让它退出, 就是要使用exit调用。 **
    还有一个问题就是为什么我们使用signal的时候, 要把它放在一开始, 而不是放在后面或者中间位置呢? 
    这是因为**signal只需要设置一次, 往后进程的生命周期里面, 所有的都有效**。那么问题是——我们的**hander方法**, **是我们的在调用signal函数的时候就调用的**, **还是后面遇到信号的时候再调用的呢**? 就比如我们和别人做约定, 我们需要遇到红灯就唱歌。那么我们是定下约定的时候就唱歌呢? 还是遇到红灯再唱歌呢?一定是我们遇到红灯时再唱歌, 那么是不是如果看不到红灯, 那么就永远不需要执行约定。——类似的, 也就是说, 我们**后续没有收到这个信号 那么这个新创建的方法, 就永远也不需要调用**。

** 注:有些信号可以被捕捉, 有些不可以被捕捉。 比如9号,19号信号。 **

键盘数据如何变成信号

** 根据冯诺依曼体系结构, 我们的进程是不能直接访问硬件, 也就是键盘的。 它必须由它的管理者直接访问, 所以我们键盘的按下, 肯定是由操作系统第一个知道的。 ——操作系统是如何知道键盘被按下, 并且把键盘里面的数据输入到自己里面的呢? 也可以换一个说法就是操作系统是怎么知道键盘上有数据了的呢**?现在看下面一张图:

    想要知道键盘有数据, 最好的方法就是操作系统定期去检查这个键盘。 因为linux下一切皆文件, 所以**键盘也是文件**, **键盘也有自己的文件描述符**, **有自己的内核缓冲区**。 **键盘读取的本质, 就是把键盘硬件的数据拷贝到键盘文件的缓冲区**里面。 
    可是计算机中的外设太多, 操作系统怎么知道我们应该向哪个外设中拿到什么数据呢?另外, 我们知道, 我们的cpu, 虽然是不和外设直接打交道的, 但是这是在数据层面。 在控制层面, 我们的cpu还是能读懂外设的。 如何读懂外设? 就是利用cpu周边的针脚, 利用一种叫做**中断号**的概念!    

针脚

  •     我们的cpu上面有很多很多的针脚, 这些针脚是集成在主板上面的, 而我们的键盘显示器内存, 各种设备也是插在主板上面的。 我们对应的键盘, 是直接能够和cpu连接到的。 我们虽然cpu不从键盘当中读取数据, 但是键盘能够从硬件层面上发送一个硬件中断。 
    
  •     也就是说, 操作系统在进行我们工作的时候, 就忙自己的, 但是一旦键盘上面有数据, 键盘就会通过硬件单元, 把我们的键盘当中的信息发送给cpu。
    
  •     那么我们要知道什么? 我们要知道的是, 从外设到内存, 再到cpu, 轮询的检查, 对于操作系统的负担是很大的。 所以操作系统不会去检查硬件里面是否有数据, 而是硬件有数据了, 通过硬件中断, 来给我们的cpu发送中断, 来让操作系统完成拷贝。 每一种中断, 都有一种中断号的概念。        
    
  •     这个中断号及类似于123456789这种数字, 假如我们的键盘的中断号是1, 未来他有数据, 就直接通过cpu连接的针脚, 向cpu内发送某个中断号, 然后针脚接收中断号的高低电频, 由cpu来解释这些电频, 确定中断号是几。 所以, 也就是说, 当键盘中有数据, cpu就能获取这个键盘的中断号, 他就记下来了。——这个过程更进一步理解就是:我们知道cpu里面有寄存器, 可以保存数据。 cpu中的寄存器凭什么可以保存数据? 本质上就是对我们的寄存器在充放电的过程,键盘给cpu发送高低电频,我们的针脚能够识别这些高低电频,  而cpu里面的寄存器里面有一个个硬件单元,这些高低电频给寄存器里面的硬件单元充放电。 并且这些电频有高有低, 到了软件层面,就被解释成为了一个个二进制。
    

中断向量表

    在我们的软件层面上, 我们的操作系统内, 比较靠前的位置, 当操作系统开机的时候, 就会生成一张中断向量表。

    这个中断向量表里面是**方法的地址**, 主要是**包括直接访问外设的方法, 主要是磁盘, 还有各种显示器, 键盘的设备**。 这里面其实放着的就是**函数指针**, 所以他会**指向操作系统当中的某个位置。 **

    所以, 我们的**外设, 一旦获取了数据, 就会通过中断单元, 将数据写到cpu的寄存器当中, cpu的寄存器就获得了一个中断号, cpu拿着中断号去操作系统的中断向量表当中寻找外设的方法**。 然后执行这个方法, 而这个方法, 才是我们的数据从外设拷贝到内存中的方法。 操作系统在将数据从外设拿到内存的时候就会判断。 判断这个数据是数据还是控制, 如果是控制, 比如ctrl + c。呢么操作系统就会把ctrl + c转化为2号信号, 发送给进程。 进程进而直接终止。 

这里有一个概念, 叫做信号的产生和我们的代码的运行是异步的。 这是为什么? **
就比如我们老师给我们上课, 如果老师这个时候口渴了, 想要喝水, 他就让学生A去买水。 这个时候
老师就和全班同学说先上自习, 等张三回来再上课。 这个过程, 就叫做同步的。 但是第二天, 老师又口渴了, 老师又让学生A去买水, 但是这个老师这次开始不等张三了, 他就继续上课。 张三呢, 随时随地可能回来, 老师不管, 老师就做他自己的工作。 两人之间你做你的, 我做我的, 互不干扰, 这叫做异步。 ——也就是说, 我们今天再运行我们的代码的时候, 他什么时候产生我们并不知道。 他可能随时随地地产生, 但是我们不需要等待这个信号。 这就说信号和我们的代码的执行是异步的。 ——这就有了信号的概念——信号是进程之间时间异步通知的一种方式, 属于软中断!**

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!

标签: linux 服务器 后端

本文转载自: https://blog.csdn.net/strive_mianyang/article/details/142477954
版权归原作者 打鱼又晒网 所有, 如有侵权,请联系我们删除。

“linux信号| 学习信号三步走 | 学习信号需要打通哪些知识脉络?”的评论:

还没有评论