前言: 本节内容主要讲解linux下信号的预备知识以及信号的概念, 信号部分我们将会分为几个阶段进行讲解:信号的概念, 信号的产生, 信号的保存。本节主要讲解信号
ps:本节内容适合学习了进程相关概念的友友们进行观看哦
目录
什么是信号
在我们生活当中, 有许多都是信号的模式。 ------比如我今天点了一个外卖, 我点了之后就在宿舍打游戏了。 之后, 外卖小哥到了, 给我打了一个电话, 敲我的门。 那么这种情况, 就叫做信号产生了 。 当我收到这个信号的时候, 我可能有事情, 那么我即便收到了信号, 也不会立即处理, 而是把事情忙完之后, 再进行处理。 也就意味着, 信号暂时得被我记录下来。 ------我们取外卖的动作, 就叫做信号处理; 外卖小哥给我们打电话叫做信号的产生; 而我们可能在做更重要的事情, 比如打游戏, 我们得等一等, 所以我们会把信号产生的这个事情记录下来, 在合适的时候进行处理, 这个就叫做信号保存。 整个流程下来, 就叫做信号产生到信号处理的生命周期!
常见的信号有哪些
我们的上课铃声, 我们的门铃, 取快递,爸爸妈妈都脸色不太好, 古代的冲锋号, 狼烟等等都叫做信号。
进程如何认识信号
虽然计算机内部有信号, 但是我们的进程是怎么认识信号的呢?
- 对于进程来说, 进程一定能够识别并处理信号的能力
- 并且, 进程即便没有收到信号, 也能知道那些信号该怎么处理。------这个信号处理能力是必须是属于进程内置功能的一部分。
- 当进程真的收到了一个具体的信号的时候, 进程可能并不会立即处理信号, 因为进程可能在做更重要的事。
- 进程当信号产生, 到信号被处理, 这个中间一定有着时间窗口, 所以进程必须有临时保存信号的能力。
所以, 综上, 我们就知道, 什么叫做认识信号?------认识信号就是能够先识别信号, 然后知道信号的处理方法。 就比如我们人, 我们知道了红灯, 门铃这些信号该干什么, 是因为我们记住了这些信号的识别方法和处理方法。
前台启动与后台启动
我们运行下面这个程序
cpp#include<iostream> using namespace std; #include<unistd.h> int main() { while (true) { cout << "i am a crazy process" << endl; } return 0; }
然后运行结果如下:
我们除了进程再运行, 其他看不出什么问题。 但是呢, 上面的进程一直在跑, 我们按什么都没有用, 所以这个时候我们ctrl + c, 就可以让进程退出。
为了更好的测试我们这里让程序睡眠上一秒钟:
cpp#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去买水, 但是这个老师这次开始不等张三了, 他就继续上课。 张三呢, 随时随地可能回来, 老师不管, 老师就做他自己的工作。 两人之间你做你的, 我做我的, 互不干扰, 这叫做异步。 ------也就是说, 我们今天再运行我们的代码的时候, 他什么时候产生我们并不知道。 他可能随时随地地产生, 但是我们不需要等待这个信号。 这就说信号和我们的代码的执行是异步的。 ------这就有了信号的概念------信号是进程之间时间异步通知的一种方式, 属于软中断!
------------------以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!