前言: 本节内容主要讲解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去买水, 但是这个老师这次开始不等张三了, 他就继续上课。 张三呢, 随时随地可能回来, 老师不管, 老师就做他自己的工作。 两人之间你做你的, 我做我的, 互不干扰, 这叫做异步。 ——也就是说, 我们今天再运行我们的代码的时候, 他什么时候产生我们并不知道。 他可能随时随地地产生, 但是我们不需要等待这个信号。 这就说信号和我们的代码的执行是异步的。 ——这就有了信号的概念——信号是进程之间时间异步通知的一种方式, 属于软中断!
——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!