文章目录
- 一、中断机制
-
- [1. 什么是中断](#1. 什么是中断)
- [2. 外部中断](#2. 外部中断)
- [3. 软中断](#3. 软中断)
- 二、理解用户态与内核态
- 三、深入虚拟地址与页表的本质
一、中断机制
1. 什么是中断
想象 CPU 是一个极其专注、按部就班的工人:
正常情况下,它从内存中一条一条取指令、执行、再取下一条------这就是顺序执行流。突然,有人拍了一下它的肩膀(中断信号),它立刻停下当前工作,转头去处理这个"急事"。处理完急事后,它又回到刚才被打断的地方,继续执行
这个拍肩膀的动作,就是中断!
中断,是CPU对异步或同步事件的响应机制,它使CPU暂停当前执行的程序,转而执行特定的处理程序,完成后返回原程序继续执行!
中断,也分为外部中断、软中断:
2. 外部中断
外部中断也叫硬件中断,它是指CPU暂停正在执行的任务,处理硬件的突发事件,如键盘敲击、网卡接收等等,结合中断号和中断向量表,执行中断处理。
中断号的提供,由硬件完成;中断向量表是一个函数指针数组,由OS提供,保存着处理中断的方法。
如果没有硬件中断方法,操作系统必须定期检查你的键盘是否要输入数据。而通过外部硬件中断,操作系统就不需要对外设进行任何周期性检查,当硬件有事情要告诉操作系统时,触发中断即可!

硬件中断中,有一种很特殊的"时钟中断":这是由CPU内部的一个时钟源,以固定频率自动触发的中断。OS正是依靠定期的时钟中断完成进程调度、时间片分配等内核定期工作的!
之前提到的alarm函数,其底层也是依赖时钟中断完成计时的。
如果没有时钟中断,操作系统一旦把 CPU 交给用户进程,就永远无法拿回控制权------除非进程主动让出(系统调用)或出错。
信号机制,是用纯软件的方法,模拟中断,完成特定任务处理的。系统内核经常利用中断来生成信号。
3. 软中断
上述的外部硬件中断,需要硬件设备触发。而为了让操作系统支持进行系统调用,CPU提供了相应的汇编指令(int 或 syscall),可以让CPU内部触发中断逻辑。这种CPU内部触发的中断,称为软中断!
使用操作系统内核的过程,就是用汇编指令(int 0x80或syscall)陷入内核,触发软中断,CPU会自动执行系统调用对应的处理方法,而这个方法会用系统调用号(本质数组下标)进程查表,执行方法!所以,内核也一定有一张系统调用表!

我们使用系统调用,没有见到int 0x80或syscall什么的,这是因为Linux的C标准库,给我们几乎把所有的系统调用封装了。
CPU内部的软中断,调用系统调用,如int 0x80, syscall,我们也称之为"陷阱"!
而除零、野指针、缺页异常这种错误,全部会转换为CPU的软中断,然后走中断处理流程。这种情况我们称之为"异常"(异常中断)!
至此,我们可以给操作系统下定义:操作系统是一个基于中断处理的软件集合!
二、理解用户态与内核态
我们已经学过程序地址分布空间:其中,0-3GB空间称为用户空间;3-4GB空间称为内核空间

每一个进程都有自己的用户级页表,但是内核级页表只有一份,被所有进程共享!这意味着,任意一个进程进行调度的时候,都能找到同一个操作系统!换句话说,操作系统系统调用的方法执行,也是在进程的地址空间内完成的!
用户态和内核态是CPU为了系统安全和稳定性而提供的两种运行级别:
- 用户态:权限较低,只能访问受限的内存区域和CPU指令。用户代码运行在此状态下。
- 内核态:权限最高,可以执行任何CPU指令,访问任何内存地址。操作系统核心(如进程调度、内存管理)运行在此状态下。
简单来说,用户态就是执行用户0-3GB空间时,访问自己的代码和数据,所处的状态;内核态就是执行内核3-4GB时,访问OS,所处的状态!它指的是CPU的执行级别。
回顾我们之前学习的信号,信号捕捉的完整流程,也涉及到多次CPU状态的变化:
收到信号后并不会立即处理,而是从内核态返回用户态的时候,会进行信号的检测和处理:

抽象为这个∞字形模型:

红色圆圈:是用户态内核态转化的四次时间节点!
- 进程从用户态进入内核态的常见方式:
系统调用、硬件中断、异常
中间黑点:检查pending信号表
- 当进程从内核态准备返回用户态时,内核会检查该进程的pending信号表,如果有未处理的信号,内核会暂停原有的返回流程,转而调度信号处理函数。
检查pending表后的循环路径:
- 无信号 → 直接返回用户态
内核检查后发现没有待处理信号,就直接恢复进程上下文,让进程回到用户态继续执行原来的代码。 - 有信号 → 先处理信号再返回
内核会把当前进程上下文保存起来,进程回到用户态,执行信号处理函数。信号处理函数执行完毕后,进程再次通过sigreturn系统调用或中断进入内核态,再次检查是否有信号需要处理。
三、深入虚拟地址与页表的本质
我们回忆一下虚拟地址和页表为什么要诞生:
如果每个用户进程直接在物理内存上申请资源,因为每一个程序的代码数据都是不一样的,按照这样的映射方式,物理内存会被分割成各种大小不同的碎片块,难以管理。
所以,我们希望操作系统提供给用户的空间是连续的,但是物理内存最好不要连续。

我们把物理内存按照一个固定的长度4KB进行分割,每个4KB称为一个页框或物理页!在32位系统下页框长度大多为4KB,64位系统则一般是8KB。
页框是一个存储区域;页是一个数据块,可以存放在任何页框或磁盘中!
可想而知,物理内存中会有大量的页框,所以操作系统一定要将其管理起来,内核就有相关的描述结构体struct page:

其中值得注意的成员有:
- flags:这个位图用来记录物理页的状态,每一位可以表示一种状态。

- count:表示在页表中有多少项指向该物理页,也就是引用计数,当计数值变为0时,说明当前页没有被使用。
页表中的每一个表项,其实都指向着一个物理页的开始地址!
假设物理内存为4GB,页框长度为4KB,则一共有一百万个页框。一个页表想要能拿到所有的物理内存,就需要一百万个项。一个项是一个4字节的地址,算下来页表的大小就为4MB。
页表也是数据,需要物理内存存储,那么存放一个页表就需要连续的1024个页框,这与我们一开始的目标好像又冲突了...
所以,真正页表的结构是多级页表!

一个进程,拥有一张页目录表和若干张页表。页目录的每一项指向着一张页表的物理地址;页表的每一项指向一个4KB的页框的物理地址。页目录和每一个页表都有1024项,即210项,每一项都是32位。
一个32位的逻辑地址,在这种页表映射结构下,会被解析为10 10 12位的内容定位物理内存:
- 前10位:用于确定页目录的某一项,定位某一张页表。
- 再10位:用于确定页表中的某一项,定位某一个页框。
- 后12位:用于确定4KB页框中的具体某一字节,通过页框地址+12位偏移量确定。
在页目录和页表中,他们只需要前20位就能完成自己的任务,所以剩下的12位一般用于作为标志位!
根据局部性原理,父子进程的写实拷贝,会把变量所在的整个4KB页框都进行拷贝!
总结一下:
页表的本质,是进程看到资源的窗口,拥有的虚拟地址越多,拥有的物理地址也越多!
多级页表的结构,减少了连续存储的要求,但也降低了查询效率!
本篇完,感谢阅读。