当前位置: 首页 > news >正文

信号(一)【概念篇】

目录

  • 1. 信号的概念
  • 2. Crtl + c 的本质
  • 3. 拓展:键盘读取背后那些事
  • 4. 异步的概念

1. 信号的概念

信号,在我们现实生活中是随处可见的,上下课铃声,大学生早八的起床闹钟,交通中的红绿灯等,这是都是信号的传递。

  • 诸如闹钟,上下课铃声这类信号,即便信号还没有产生,我们也知道信号产生之后,我们应该做什么事情(比如现在还没上课,但是当上课铃声响了,我们知道我们该回教室了;晚上睡前,我们定了一个闹钟,此时闹钟还没响,但是我知道,当它明早响起那一刻,我应该起床去上那个 b 课了)。

  • 但是,理想很丰满,现实很骨感。很多时候信号产生那一刻,我们可能并不会立刻去处理它,而是会选择一个合适的时间再去处理,因为可能当前有更重要的事情要做( 比如上课铃声响了,正常来说我应该立刻回到教室中去,但是耐不住我此时在厕所开大啊,难不成我开一半去上课吗???我是不是得拉完再去上课)。而信号产生之后、处理信号之前的这段时间,我们称为时间窗口,在这个时间窗口内,我们需要保存信号(记住信号)(上课铃声响了,我在上厕所,不能立马回教室,那我是不是得记住我等会上完厕所我是要去上课的啊,总不能拉完就忘了吧?!这就是保存信号)。

  • 那我们是如何认识这些信号的呢?像日常中随处可见的信号,从小就灌输在我们脑海里的知识,所以我们认识它们。而认识一个认识的本质是,能够识别信号是什么,以及信号产生后的处理方法。(我不仅需要知道上课铃声是预示着要上课了,我还得知道,这个铃声响起来后,我应该做什么)

而把上述这些信息代入到计算机,就是进程必须能够识别 + 处理信号,无论信号产生与否,都要具备处理信号的能力,因此对信号的处理能力,属于进程内置功能的一部分(能识别什么信号,每种信号的默认处理方式是什么等内置功能)。这也是为什么当向一个进程发送 9 号信号 kill -9 时,能够终止一个进程,因为当进程被创建时,这个信号已经内置在进程中了,进程天然的就认识 9 号信号,并且具备处理 9 号信号的能力。

对于进程,它收到信号时,也可能不会立即处理,然后选择合适的时机再去处理。而在信号产生到信号处理期间,会有一个时间窗口,对于进程,同样要具备临时保存哪些信号已经发生了的能力!

2. Crtl + c 的本质

  • Crtl + c 为什么能够杀掉进程呢?
    在 linux 操作系统,一次登录就启动一个终端,一个终端一般配一个bash,每一次登陆,只允许一个进程是前台进程,可以允许多个进程是后台进程。

    前台进程运行起来时,此时我们输入其它任何指令,不会得到 bash 的响应,因为此时 bash 变为了后台进程,我们的程序在前台运行着。可以理解为,谁能够获取键盘输入,谁就是前台进程。这也是为什么 Crtl + c 可以杀掉进程,因为当我们的进程运行起来,变为前台进程,它能够获取键盘输入,Crtl + c 指令就被解释为终止进程的行为。

    我们也可以验证这一点,前台进程运行起来,我们的 bash 就无法获取指令了。

    在这里插入图片描述

    而当我们把进程以后台的形式运行,那么 Crtl + c 就无法杀掉该进程,后台进程只能用 kill -9 终止。因为键盘输入的信息传递不到后台进程,此时的前台进程是 bash,Crtl + c 要杀也是杀掉 bash,只不过 bash 内部对该指令做了特殊处理,不会被 Crtl + c 终止掉而已。并且我们可以看到,我们自己的程序以后台形式运行,我们的命令行终端依旧可以获取 ls、pwd 这些指令,这就足以证明前台进程是 bash 了,我们自己的进程不在前台运行。

    在这里插入图片描述

    这也可以更进一步理解,前台和后台的本质,就是谁能够获取键盘输入。一开始我们的进程在前台运行,因此可以获取键盘输入的 Crtl + c ,然后终止进程。后来我们的进程在后台运行,获取不到键盘输入,因此 Crtl + c 无法结束进程,而同时,我们的进程在运行,往显示器输出的时候,由于此时的前台还是 bash 进程,因此这不并不影响我们继续给 bash 输入其它指令,依旧是可以正常运行显示的。(只不过后台进程在往显示器打印,前台 bash 也在获取指令输出信息时,屏幕的信息注定是杂糅在一起的,因为在显示器文件只有一个,它是共享资源)。

    并且如果我愿意,启动完一个后台进程之后,前台的 bash 还在,我可以继续启动其它的后台进程。即便这中间可以输入的指令一下子被其它进程的输出信息刷走了,也没关系!只要我键盘输入了,bash 进程就能够获取,bash 进程获取到我输入的数据即可,乱不乱的无所谓。

我们在 进程的创建、终止 谈过进程终止时,就说过一个进程异常终止的本质是收到某种信号,因此对于 Crtl + c 终止进程的本质也一定是对进程发送某种信号。 向一个进程发送 Crtl + c 指令,最终都会被解释为 2 号信号(SIGINT)。

[outlier@localhost signal]$ kill -l1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

上面说过,一个进程收到信号后,可能不会立即处理,因此需要保存信号,后续再处理信号。那么信号是怎么处理的呢?

信号的处理方式:

  • 默认动作(比如收到红灯信号,一般人的默认行为都是等待)
  • 忽略(但也不凡有人直接忽略红灯,闯了过去)
  • 自定义动作(还有其它人,可能会有别的动作,比如旁边有一家便利店,顺便去买瓶水喝)

下面我们来验证: 向一个进程发送 Crtl + c 指令,最终在进程上是收到了 2 号信号,然后终止自己。

NAMEsignal - ANSI C signal handlingSYNOPSIS#include <signal.h>typedef void (*sighandler_t)(int);			// 函数指针sighandler_t signal(int signum, sighandler_t handler);			// 修改进程对于指定信号的处理动作参数分析:
signum:想要捕捉的信号编号
handler:信号处理时的自定义动作,传递一个函数地址,函数内编写的就是处理该信号的方法。
#include<signal.h>
// int: 指明收到的信号 
void myhandler(int myhandler)
{cout << "process get a signal: " << signo << endl;// 对信号做了自定义处理,那么就不会再触发信号的默认处理动作了// 因此如果我们自定义处理信号时,不手动提出,那么 Crtl + c 不会终止进程// 换言之,信号的处理是三选一的,信号默认处理时就不会有自定义处理,有了自定义处理就不会有其它的处理了。exit(1);	
}int main()
{// 信号捕捉,只要设置一次,往后代码执行中一直有效signal(2, myhandler);       ....return 0;
}

在这里插入图片描述

结合我们信号概念的阐述,我们要清楚一点,当我们捕捉了信号那一刻,myhandler 自定义信号处理方法并不会被调用,而是执行后续代码时,收到了我们捕捉的信号时,才会触发信号处理的方法,然后执行信号处理。

3. 拓展:键盘读取背后那些事

根据冯诺依曼体系结构,键盘属于外设,无法被进程直接访问,只能通过操作系统来访问,因此当用户使用键盘时(键盘被摁下那一刻),第一个获取到这个事件信息的一定是操作系统。

因为 linux 下一切接文件,所以键盘也是文件,有自己的 struct file 和文件缓冲区,因此从键盘中读取数据的本质,就是把键盘中的数据写入到键盘文件的缓冲区中,再由操作系统拷贝到用户缓冲区。

所以问题就是:操作系统如何得知键盘中有数据了?

最简单的方法就是操作系统对键盘做轮询,时不时去查看键盘,但是一台计算机的外设多了,这样检测硬件状态的话,效率是非常低下的。在冯诺依曼体系结构中,在数据层面上,cpu 不与外设交互,但是在控制层面上,cpu 是能够获取外设的各种信息的。换言之,虽然 cpu 不能直接从键盘中读取数据,但是在硬件层面,键盘可以向 cpu 发送硬件中断信号。

在一台计算机中可不止一个键盘外设,还有诸如显示器、鼠标、网卡等,每一种外设有了数据,这些数据被 cpu 计算处理之前,都需要先由操作系统把外设的数据拷贝到内存中,而问题的难点不在如何拷贝,而在何时拷贝(因为操作系统不知道外设什么时候会有数据),而如果操作系统采取轮询的方式,那么外设多了,操作系统的效率绝对低下。

所以每种外设都可以向 cpu 发送硬件中断,而 cpu 为了区分各种外设的硬件中断,每一种硬件中断就引入了中断号的概念(中断号就是一个数字)。CPU里面是有很多针脚的,外设给 cpu 发送中断,就是把中断号传递给 cpu(假设一号中断),它的本质就是向(一号)针脚发送高低电平(即对针脚充放电),然后把中断号存储在 cpu 的寄存器内,这就完成了外设通知 cpu 数据就绪的工作。

关于操作系统如何得知键盘中有数据了,以上是在硬件层面上做的工作,在软件层面上,操作系统启动时,会创建一张中断向量表(一个数组),表中存储指向外设的读写方法,主要包括硬盘、显示器、键盘等。所以当 cpu 收到了外设的中断号,操作系统就能够识别到 cpu 内存储的中断号,然后根据中断号索引,在中断向量表中找到对应读写方法, 把数据拷贝到内存中(即外设文件的文件缓冲区)。(注:每个外设都具有唯一的中断号,并且都会在中断向量表中,属于自己的中断号索引处,注册外设的读写方法)

所以关于操作系统如何得知键盘中有数据了,一套流程下来就是:键盘数据就绪 --> 发送中断 --> cpu 收到中断并记录 --> OS 识别中断,并在中断向量表中索引找到键盘读写方法 --> 把键盘的数据拷贝到内存。

这样一来,操作系统就再也不需要因为外设数据何时就绪而苦恼了,它只需要默默等待外设向 cpu 发送中断即可,cpu 收到外设发送的中断,操作系统就能够识别然后确定外设有数据了。

键盘中按键有些是用于控制的,比如 Crtl + c 这样的组合键,当操作系统读取键盘数据时,是会判断输入的是数据还是控制 ,像读取到类似 Crtl + c 这样的组合键时,操作系统会将其转换为 2 号信号发送给进程,从而达到终止进程的目的。

诸如文件等外设读取的操作,open 打开一个文件,进程做 read 读取,在读取数据之前,磁盘需要先寻址(这个过程中,进程处于阻塞状态,等待外设资源就绪中),但是当磁盘找到了该文件之后,操作系统如同读取键盘数据,并不知道磁盘找到该文件的数据了,因此当磁盘数据就绪了,磁盘也会给 cpu 发送硬件中断,然后操作系统再唤醒进程,把进程变为运行态,最后进程读取数据。而当文件数据读完时,操作系统也不知道数据拷贝完成了,因此磁盘会再次向 cpu 发送中断,以示读取文件完毕。

4. 异步的概念

前台进程在运行过程中,用户是随时可能按下 Ctrl + C 这样的组合键的,而这种键盘数据会被操作系统解释为信号,因此操作系统会向进程发送 2 号信号,然后进程被终止。也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步的。

何为异步?

管道通信就不是异步的,读端需要兼顾写端,当写端不做写入操作时,读端会进入阻塞状态,等待写端向管道写入数据;当写端写的有点慢,隔一段时间写一次,读端并不会一直读取管道中的数据,而是等待写端下一次写入数据后,再进行读取,这就是同步。

而像没有加入任何保护机制的共享内存通信,就是一个典型的异步。两个进程使用共享内存通信时,不管写方有没有向共享内存中写入数据,读方都是一直读取这块内存中的数据;当写方写入一段数据后,然后间隔一段时间不做任何写入操作,此时的读方进程也不会等待写方,还是会一直读取共享内存中的数据。总而言之就是,你写你的,我读我读,你不管我,我也不管你,这就是异步!

代入我们一开始说的,信号相对于进程的控制流程来说是异步的。在我们调用 signal 函数捕捉信号时,信号此时并没有产生,因此代码继续往下执行,进程并没有去等待信号的产生。但是,说不准执行到哪一行代码处,可能突然就收到了 SIGINT 信号,然后进程终止了,即信号也不管进程的代码有没有执行完,只要信号来了,这个进程就必须立刻受到信号的影响,信号不会兼顾进程,这就是信号对于进程的控制流程是异步的。

因此,信号是进程之间事件异步通知的一种方式,属于软中断。信号就是以软件的方式,对进程模拟的硬件中断!(操作系统是不知道我何时会在键盘按下 Ctrl + C,就如同操作系统不知道键盘何时有数据是同个道理!而当我按下了 Ctrl + C,进程会收到 SIGINT 信号,就如同键盘数据就绪了,会向 cpu 发送硬件中断)


如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!


http://www.mrgr.cn/news/49946.html

相关文章:

  • transformers不指定版本能装上,指定版本transformers==4.15.0装不上
  • NVIDIA RTX A1000 显卡测评 Thinkpad P16v 测评
  • 04 django管理系统 - 部门管理 - 新增部门
  • 在java 中如何实现执行linux命令,通过post接口代理出来?
  • 【Hadoop】HDFS基本操作
  • JavaScript 常用方法
  • 牛客网上最全的Java八股文整理,涵盖Java全栈技术点
  • 如何下载3GPP协议?
  • 智慧交通:科技保障出行安全
  • 智慧灌区信息化管理系统解决方案
  • Libevent源码剖析-开篇
  • ffmpeg截取一段视频中一段视频
  • 从源码到平台:使用视频美颜SDK构建高性能直播美颜系统详解
  • 怎么把wma格式转换成mp3?介绍几种wma转换成MP3的转换方法
  • 麦沃德提升会议效率与质量:一款全面的会议管理系统
  • RuntimeError: Unable to find a valid cuDNN algorithm to run convolution
  • Redis日常运维
  • 前后分离项目记录
  • FrameWork使用EfCore数据库映射举例
  • 【live2d】看板娘人物模型使用方法