本篇文章,继续与大家分享与Linux相关的知识。本次的主要内容会涉及到什么是信号以及信号是如何产生的。

下图是我们了解认识信号的一个过程

Linux-信号的产生_信号的产生

举个生活中的例子,便于大家理解上图。在打游戏前,你点了一个外卖。过了一会,你听到敲门声,知道是外卖来了。可是,你还在打游戏,就没有去开门。又过了一会,你打游戏结束了。你没有接着去打游戏,而是去开门取外卖。

在这个过程中,敲门声就是信号的产生,告诉我们外卖到了。但这个时候,我还有更重要的事要做,就是打游戏。所以,我只是记住外卖到了,也就是信号保存,没有马上去处理。

等游戏打完了,去取外卖。这叫信号处理。

预备

我们平时见过的信号有哪些

信号弹,下课上课的铃声,求偶,红绿灯,旗语等等

那你是怎么认识这些信号的呢?答:有人教,你记住了。

对于下课铃声这些信号,即便是我们现在没有信号的产生,你也知道信号产生之后,你该干什么。

当信号产生了,我们可能并不会立即处理这个信号,而是等一个合适的时候。因为我们可能正在做更重要的事情。所以,信号产生之后,和信号处理之前,有一个时间窗口,在这个时间窗口内,你必须记住信号的到来。

那怎么样才算认识信号呢

1.识别信号,也就是知道它是个信号。比如说红绿灯的红色,绿色

2.知道信号的处理方法。比说红绿灯的红灯,表示停,你需要把车停下来

在生活中,处理信号的是你。那到了操作系统中,是谁你?当然是进程啦!

我们以进程来理解刚刚我们所说的概念,总结为以下四点

1.进程必须 识别+能够处理信号 -- 信号没有产生,也要具备处理信号的能力 -- 信号处理能力,属于进程内置功能的一部分

2.进程即便是没有收到信号,也能知道那些信号该怎么处理

3.当进程真的收到了一个具体的信号的时候,进程可能并不会立即处理这个信号,合适的时候

4.信号产生到信号开始被处理,就一定会有时间窗口,进程必须具备临时保存哪些信号已发生了的能力

Linux-信号的产生_信号_02

我们来聊聊快捷键Ctrl+C

Ctrl + C,我们都认识,这个组合键能终止进程。它是怎么做到的呢?

我们写一个死循环程序:

Linux-信号的产生_信号的捕捉_03

Linux-信号的产生_信号的产生_04

编译运行,程序就会每隔一秒向显示器打印I am crazy process,在程序打印的过程中,不管我们输入什么指令,都是无效的。也就是,一旦点斜杠启动程序了,bash就不再接受任何指令了。我们把这种进程称之为前台进程

Linux-信号的产生_信号的产生_05

如果说我们在启动程序的时候,带上取地址符。指令同样能跑,也会每隔一秒向显示器打印信息。但当我们使用Ctrl + C组合键去终止这个进程时,会发现组合键没有用。这种进程我们称之为后台进程

Linux-信号的产生_信号的产生_06

Linux,一次登录,会有一个终端,一般会配上一个bash,每一次登录,只允许一个进程是前台进程,可以允许多个进程是后台进程。

下图,这种场景相当于我们打开了两个终端,也就是登陆了两次

Linux-信号的产生_信号_07

我们用的云服务器,可以理解为把bash放在前台,允许它接受指令。

通过刚刚前台和后台运行myprocess程序的结果来看,我们不难发现,键盘输入的数据由前台进程来接受。这就解释了,为什么我们刚刚将myprocess程序放到后台运行后,组合键Ctrl + C失效了。因为myprocess程序放在后台运行,没法接收到Ctrl + C。

那问题又来了,为什么bash收到了Ctrl + C,并没有退出呢?这是因为bash做了特殊处理。

我们将程序myprocess放到后台运行后,我们输入ls指令依然是有效的,虽然说回显的信息是乱的,但实际上键盘的输入并没有乱。

Linux-信号的产生_信号的产生_08

为什么显示器看着是乱的呢?这是因为操作系统在把键盘文件中的数据拷贝到显示器文件缓冲区时,后台进程myprocess也在向显示器文件缓冲区写入数据。两者都在向显示器文件缓冲区写入,可显示器文件缓冲区并没有加保护。数据混乱很正常。‘

哪为什么实际上没有乱呢?键盘和显示器都有自己的文件缓冲,我们键盘输入的数据是先放到键盘的文件缓冲区,然后,拷贝到显示器的文件缓冲区,进行显示。这是数据回显的过程。向显示器文件缓冲区写入数据的人,又不止键盘一个,数据乱很正常,但那和我键盘有什么关系,我有自己的文件缓冲区。

Linux-信号的产生_信号_09

那OS是怎么知道键盘中有数据的?CPU周围有很多的针脚,键盘会通过中断元件,以硬件中断的形式,告诉CPU键盘有数据了。可以引起硬件中断的设备不止一个,这么多硬件中断,CPU怎么区分那个硬件中断是那个设备引起的?于是,就有了中断号的概念,中断号和数字1,2,3类似

思考两个问题:键盘是如何把数据给操作系统的?Ctrl + C又是如何解释成2号信号的?

当键盘上有数据了,就会引起硬件中断,通知CPU。操作系统根据CPU中记录的数字,索引到中断向量表中对应的位置,执行访问外设的方法。在对键盘中的数据操作之前,操作系统会先判断,键盘输入的是信号还是纯数据。如果说是给进程的数据,数据会先被拷贝到OS提供的文件缓冲区,再通过scanf,write读到进程中

中断向量表在操作系统中比较靠前的位置,在开机启动的时候,自动形成。中断向量表中都是方法的地址,方法主要是直接访问外设的方法。

CPU中的寄存器也是具有存储能力的,它凭什么能存储数据呢

可以简单理解为有32个bit,每个bit位对应一个硬件单元,把其中一个硬件单元冲成高电位,低电位表示0,高电位表示1。比如0000 0000 0000 0001 0000 0000 0000 0000。所以,此时CPU的寄存器就有了数据

CPU和键盘的交流,只局限于控制层面。数据层面,它们之间没有交互。如果CPU过多的参与IO效率会低,计算机中有一个专门负责IO的芯片,叫DMA。

Linux-信号的产生_信号的捕捉_10

那为什么Ctrl + C组合键能杀掉前台进程呢?第一点:键盘输入首先被前台进程收到。第二点:Ctrl + C本质是被进程解释成为收到了,2号信号。2号信号的默认动作,就是终止自己

信号

信号是模拟硬件中断的纯软件行为,属于软中断。我们可以使用kill -l指令来查看有哪些信号

kill -l

信号本质就是数字,数字旁边的大写英文是宏。信号中没有0,32,33号,一共62个。32号之前的信号,我们称之为普通信号,34~64号是实时信号。实时信号就是收到信号后,需要马上处理

Linux-信号的产生_信号的产生_11

我们主要学习了解普通信号。收到信号之后,我们有三种处理方式:

一是默认动作,也就是系统本身提供的处理方式。比如说2信号,系统的默认动作是终止进程

二是忽略,也就是我们收到信号后,什么也不做

三是自定义动作,我们又把自定义动作称之为信号捕捉。自定义动作,就是我们自己设置收到信号后,进程需要进行的动作

Linux-信号的产生_信号的产生_12

那我们怎么去进行信号捕捉呢?

信号的捕捉,signal

认识一个新的函数signal。它的第一个参数是传信号,可以直接传数字,也可以直接传信号对应的宏。第二个参数是函数指针,也就是当进程收到对应的信号后,需要执行的函数

Linux-信号的产生_信号的捕捉_13

Linux-信号的产生_信号的产生_14

下面,我们来验证Ctrl + C组合键是给进程发送2号信号

Linux-信号的产生_信号_15

编译运行,Ctrl + C,我们发现程序确实收到了2好信号,可为什么没有终止呢?因为自定义动作和默认动作是并列的,有了自定义动作,默认动作就失效了。而我们定义的自定义动作,并没有让程序终止。

Linux-信号的产生_信号_16

如果我们想让程序进行退出,可以加上exit函数的使用

Linux-信号的产生_信号的产生_17

这样程序在收到2好信号后,就会退出了

Linux-信号的产生_信号的产生_18

signal函数只需要设置一次,从它的位置往后都是有效的。那它什么时候起作用呢?在收到信号的时候,并不是你设置它,它就生效了。

为什么handler函数要有一个信号参数呢?收到信号后,执行handler不就可以了吗?你设想一下,如果我们接受到的所有信号,都调用handler这个函数怎么办?是不是,我们就可以利用signo这个参数,确认我们具体收到的是哪一个信号。

信号具体什么时候产生,我们也不知道,它是进程之间事件的异步通知的一种方式。

什么是异步?举个例子:小张课上睡着了,老师讲课感觉嗓子有点干,就说小张,你去楼下的小卖部帮我买瓶水。在小张出去买水的这段时间,老师让大家自习等小张回来,这叫同步。如果说在小张出去的时间里,老师接着继续上课,这叫异步

信号的产生

所有的信号产生都可以被捕捉吗?我们可以一一进行尝试捕捉,最后,我们不难发现,9号和19号信号是不能被捕捉的。

为什么?首先,是9号。如果说允许所有的信号被捕捉,那么所有进程都杀不掉了。有一天来了个恶意软件,捕获了所有的信号,那还得了。

Linux-信号的产生_信号的捕捉_19

19号信号的功能是暂停进程。一个进程进行的工作很重要,但如果它稍微失控,你不想让它跑了。此时,你又不能杀掉它,但可以暂停它,将影响降到最低

信号的产生有三种方式:

1.键盘组合键

Ctrl + C 相当于2号 Ctrl + \ 相当于3号

2.kill命令

kill -signo pid

3.系统调用

kill函数

Linux-信号的产生_信号_20

我们可以使用kill函数配合getpid函数给自己发送信号

Linux-信号的产生_信号_21

编译运行,当cnt减为0的时候,进程就收到了2号信号退出了

Linux-信号的产生_信号_22

raise函数

raise函数的使用方法更加简单,它默认是给调用它的进程发送信号。也就是谁调用它,它就立马给调用它的进程发送一个信号。

Linux-信号的产生_信号的捕捉_23

Linux-信号的产生_信号_24

编译运行,五秒之后,进程就收到2信号终止了

Linux-信号的产生_信号_25

abort函数

Linux-信号的产生_信号的捕捉_26

调用abort函数会给调用它的进程发送6信号

Linux-信号的产生_信号_27

Linux-信号的产生_信号_28

编译运行,过五秒,进程就会收到六号信号,然后,进程被终止

Linux-信号的产生_信号的捕捉_29

由于操作系统版本的不同,信号部分的一些特定化的定义没体现出来。信号部分有很多特定化的定义。

无论是三种方式中的哪一种,都是操作系统提供给我们信号产生的方式。

信号无论如何产生,最终一定是谁发给进程的?答:操作系统。为什么?因为OS是进程的管理者。

Linux-信号的产生_信号的产生_30

模拟kill指令

mykill.cc

Linux-信号的产生_信号_31

写一个程序myproc.cc来给mykill.cc杀掉

Linux-信号的产生_信号的捕捉_32

编译运行,就可以完成杀进程的任务了

Linux-信号的产生_信号的捕捉_33

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和Linux相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。