本章节将会围绕信号处理进行展开讲解
目录
- 回顾一下:
- 历史问题:
- 地址空间:
- 键盘的输出如何被检测到:
- 持续更新...
回顾一下:
信号处理也就是信号递达
我们说过递达时一种有3种行为
- 默认行为
- 忽略行为
- 自定义行为
历史问题:
我们其实一直都存在一个问题:一般信号发送时不会被立即处理,而是等到合适的时候进行处理,那么这个合适的时候究竟是什么时候?
先说结论:从内核态返回到用户态时进行处理。
这两个名词等会会有解释,现在重要的是先将脉络理清楚,在去深究细节。
返回时我们会先看此时pending表有没有为1的信号,在看是否阻塞,如果没有,那么我们就会执行handler函数,执行handler函数我们一般有3种行为
- SIG_DFL :默认行为,大部分进行都是进行终止,如果是终止的话,我们OS在内核态会直接杀死进程,不会返回到main函数了。
- SIG_ING:忽略行为,把对应的1->0,返回到main函数继续执行
- 自定义:我们一般把这种情况叫做信号捕捉。
也就是下图
那我们现在就要理清一下这张图的逻辑。
首先,我们因为某些系统调用等情况进入到内核,当处理完进入到内核的情况后会检查信号,观察pending与block位图是否符合要求,如果是默认OS在内核态会直接杀死进程,不会返回到main函数了;如果是忽略把对应的1->0,返回到main函数继续执行;但是最恶心的是自定义行为,也就是我们这样图所示。
此时我们面临一个问题,OS可以直接去执行用户的代码吗?
答案是否定的,如果用户进行了exec等系列函数,或者代码出错,那么OS岂不就完蛋了吗,但是在技术角度肯定可以实现,但是仍然不会给你实现。
那就必须进行状态切换,让用户自己执行自己的代码,自己出错自己负责。
那么我们可以直接从第四步执行到第一步吗,
答案也是否定的,最直观的就是可以看到我们的handler函数并没有调用main函数,也就是没有直接的相互调用关系。
此时需要使用一些特殊的系统调用函数,返回内核再返回main函数继续执行。
这样我们就·大概了解了信号捕捉的流程。一个无穷符号。
对于如何快速记忆,我们可以先画一个无穷符号,在焦点的上方画一条横线。
横线上方是用户态,下方是内核态。
到现在为止要开始暂停一下了,我们要开始讲几个子问题,知道这些才能搞定内核态与用户态的区别。
地址空间:
我们先来看一个大概的图。
对于程序地址空间我们已经接触过很多次了,在自定义函数中会在正文代码内跳转,在申请内存时又会在堆中跳转,在使用动静态库时会在共享区跳转。综合来看也就只有内核[3,4]GB我们还没有接触过。
我们已经使用过很多次系统调用了,系统调用时函数,那么就会有函数地址,可是我们从没交过函数地址,那么他在哪里呢?
注意:OS是第一个在内存中被加载的软件,另外我们其实还有一份页表是内核级的,专门负责OS的映射。
这也就意味着:OS本身就在我的地址空间中。
但也会有很多个进程同时存在的情况。
用户级的页表每个进程都有一个,但是内核级页表只有一个,他们是按照统一的方式进行映射
这也就意味着,不论进程如何切换,我们总能找到OS,通过访问[3,4]GB的进而找到所有的代码和数据,就可以进行系统调用了!
所以访问系统调用其实是和访问库函数没有区别的,都是在地址空间中进行的。
而OS不会让用户直接进行访问内核的部分,要受到约束,所以要使用OS提供的系统调用进行访问!
键盘的输出如何被检测到:
我们先来看一个简图。
那么问题出现了,当我们按下键盘时,OS是通过何种方式得到我们的数据呢?
方法一:OS进行轮询–>结果:累死。
方法二:也是那些计算机软件科学家发明出来的。
根据冯诺依曼体系我们知道CPU不会和硬件直接打交道(数据层面),
但是当我们按下键盘的时候,会向CPU发送硬件中断(控制信号),每个硬件都有自己的中断号(假设键盘为3)。
CPU有很多针脚,也就是图中的红色凸出部分,我们向特定针脚发送高电平,CPU接受到信号就会将对应的中断号放入到对应的寄存器中,此时就变成软件了!只需要对软件进行操作即可。
那我们现在以软件的角度进行观察,第一个被加载的软件是OS,实际上,OS会首先形成一分函数指针数组,这些函数实际上是OS的一些方法,包括但不限于硬件的读写方法。
,所谓的中断号其实也就是对应的下标。
从此,当我们进行摁键盘的操作时,CPU会把当前所有的硬件任务停止,把中断号读到并去索引对应的方法下标。
这个表叫做中断向量表,所有的外设都是这样的!