笔记:现代操作系统:原理与实现(1)
第二章 硬件结构
冯诺依曼结构组成部分
- 中央处理单元(Central Processing Unit, CPU):主要负责运算和逻辑控制。按照程序中的指令进行计算,并且根据条件执行程序中的不同部分(顺序或跳转执行)。
- 存储器(memory unit):负责存储程序指令和数据,以及保存程序执行的中间结果和最终结果。在现代计算机中,存储器通常包括寄存器、CPU 缓存、内存等存储层次。
- 输入输出(Input and Output, I/O):负责与外界进行交互,从外界获得输入,将结果向外界输出。诸如键盘、鼠标、显示器、打印机以及网卡等外设,都属于此类设备。
CPU与指令集架构
指令集架构(Instruction Set Architecture, ISA)是 CPU 和软件之间的桥梁。ISA 包含指令集、特权级、寄存器、执行模式、安全扩展、性能加速扩展等诸多方面,本节将对相关内容进行介绍。
指令集
指令集是 ISA 的重要组成部分,通常包含一系列不同功能的指令,用于数据搬移、计算、内存访问、过程调用等。
以Aarch64为例,其指令集属于精简指令集计算机(Reduced Instruction Set Computer, RISC),每条指令的长度固定为 4 字节,指令类型包括:
- 数据搬移指令(如 mov);
- 寄存器计算指令(如加法指令 add、减法指令 sub);
- 内存读写指令(如内存加载指令 ldr、内存写入指令 str);
- 跳转指令(如无条件跳转指令 b);
- 过程调用指令(如调用指令 bl、返回指令 ret);
- 特权指令(如读取系统寄存器指令 mrs、写入系统寄存器指令 msr)等。
ARMv8 支持两种执行模式——AArch32 与 AArch64,前者主要为了向后兼容,本书主要关注后者,即支持 64 位虚拟地址的 AArch64,为了方便表述也称之为 AArch64 体系结构。
x86-64 体系结构的指令集属于复杂指令集计算机(Complex Instruction Set Computer, CISC)。通常来说,相比于 RISC,CISC 具有指令数量更多、指令编码长度可变和指令寻址方式多样等特点。
add: 用于执行加法运算adrp : Address Page - 计算页面对齐的地址,页面大小通常是 4KB = 0x1000
str : 将寄存器中的数据存储到内存地址str w0, [x1] : 将 w0 存储到 x1 指向的地址
stp : STP 用于将两个寄存器的值存储到内存中,通常是连续的两个内存地址上。
strb : 将一个源寄存器中的最低一个字节(8 位)的数据,写入到内存中的某个地址 b <label>/<address>: 无条件跳转到指定label或地址
br : 间接跳转:跳转到寄存器中存储的地址
cbnz: 比较寄存器 Xt 的值是否为 非零,非零则跳转
b.eq\b.ne : 与cmp组合使用,相等/不等则跳转
b.gt : 比较是否大于nop : No Operation
gdb命令
p/x *(char **)语法含义:
/x打印16进制,/s打印字符串,/d打印十进制
* :解引用操作符,获取指针指向的值
(char **) : 对于**来说,char与int\double没有区别,主要易于理解;**地址中存储的为指针,*的话为存储的是具体的变量值
特权级
AArch64 中的特权级被称为异常级别(Exception Level, EL),共有四种特权级。
- EL0:最低的特权级,应用程序通常运行在该特权级,也称为用户态。
- EL1:操作系统通常运行在该特权级,也称为内核态。
- EL2:在虚拟化场景下需要,虚拟机监控器(Virtual Machine Monitor, VMM,也称为 Hypervisor)通常运行在该特权级。
- EL3:和安全特性 TrustZone 相关,负责普通世界(normal world)和安全世界(secure world)之间的切换。
由于通常应用程序运行在 EL0 而操作系统运行在 EL1,所以主要介绍 EL0 与 EL1 之间的常见切换场景。一般来说,从 EL0(应用程序)切换到 EL1(操作系统)的可能场景有三种:
- 应用程序需要调用操作系统提供的系统调用,此时应用程序会通过执行 svc(特权调用,supervisor call)指令将 CPU 特权级从 EL0 切换到 EL1。(同步)
- 应用程序执行了一条指令,而该指令触发了异常(exception),该异常导致 CPU 特权级从 EL0 切换到 EL1。例如,应用在执行一条访存指令时,触发了缺页异常(page fault),从而切换到操作系统内核进行处理。(同步)
- 应用程序在执行的过程中,CPU 收到一个来自外设的中断(interrupt),该中断也会导致 CPU 特权级从 EL0 切换到 EL1。(异步)
EL0和EL1之间切换的基本流程
在此过程中CPU保存的内容包括:
- 触发异常的指令地址,即程序计数器(PC)
- 异常原因
- 保存SP_EL0(应用程序使用的栈指针)
- CPU相关状态
当发生特权级切换时
- CPU 会读取 VBAR_EL1(向量基地址寄存器,Vector Base Address Register)来获得异常向量表(exception vector table)的基地址
- 根据异常原因(ESR_EL1 中保存的内容)调用操作系统设置的相应异常处理函数。
- 操作系统中的相应异常处理函数开始执行,
- 在异常处理完成后,操作系统会恢复应用程序的上下文。
- 执行 eret(异常返回,Exception Return)指令以恢复 CPU 自动保存的 EL0 状态(包括 PC 和 SP 等)
- 并切回到 EL0,使应用程序从被中断处继续执行。
寄存器
在 AArch64 中,有 31 个 64 位通用寄存器,被命名为 X0~X30。其中,
- X29 用作帧指针(Frame Pointer, FP)寄存器,用于保存函数调用过程中栈顶的地址
- X30 用作链接指针(Link Pointer, LP)寄存器,因为 CPU 在执行函数调用指令 bl 时会自动把返回地址保存在其中。
物理内存与CPU缓存
CPU 使用物理内存的方式很简单:通过总线向物理内存发送一个读写请求,其中包含目标地址(若是写请求,则还包括写入值),物理内存收到请求后进行读写操作(若是读请求,则将读取值发回 CPU)。
但相比于CPU处理的速度,内存访问速度是非常缓慢的。所以在CPU中引入了缓存。
- 当 CPU 需要向物理内存写入数据的时候,它可以直接写在 CPU 缓存之中
- 当 CPU 需要从物理内存读取数据的时候,它可以先在 CPU 缓存中查找,如果没找到再去物理内存中获取,并且把取回的数据放入缓存中,以便加快下次读取速度。
缓存结构
CPU 缓存是由若干个缓存行(cache line)组成的。每个缓存行包括:一个有效位(valid bit),用于表示其是否有效;一个标记地址(tag address),用于标识其对应的物理地址;一些其他的状态信息。
通常,CPU 以缓存行(常见的是 64 字节)为单位把物理内存中的数据读取到 CPU 缓存中,也就是说即使只需要单个字节的值,该字节对应的缓存行也会全部进入缓存中。同样,将数据写回到物理内存也是以缓存行为单位的。
典型的 CPU 缓存结构如图 2-6 所示。为了通过内存的物理地址找到对应的缓存,物理地址在逻辑上分为 Tag、Set(也称为 Index)以及 Offset 三段。组(Set)与路(Way)是 CPU 缓存的经典概念。物理地址中的 Set 段能表示的最大数目称为组。例如,如果 Set 段的位数是 8,那么对应的 CPU 缓存的组数就是 256(2⁸ = 256)。同一组(即 Set 段相等)下,支持的最大 Tag 数则称为路,即同一组下的缓存行数目。例如,在图 2-6 中,在 Set 相同的情况下,缓存最多支持 4 个不同的 Tag,也就是 4 路。该 CPU 缓存被称为 4 路组相联(4-Way Set Associative)。
缓存寻址
下面以支持 AArch64 架构的 Cortex-A57 CPU 中的 L1 数据缓存为例,给出 CPU 缓存查找的一般过程。该 CPU 缓存的相关参数如下:
- 物理地址的长度为 44 位;
- 缓存大小为 32 KB,缓存行大小为 64 字节;
- 256 组,2 路组相联缓存。
假设要读取以物理地址 0x2fbbc030 开始的 4 字节的物理内存数据。首先计算offset、set、tag的位数:
- **Offset:**CPU使用字节寻址,offset = log2(64) = 6位
- **Set:**set与组数相关联,set = log2(256) = 8位
- **Tag:**物理地址为44位,tag = 44- 8 - 6= 30位,由于 0x2fbbc030共32位,所以进行高位补零
得到Tag 为 0xbee f,Set 为 0x0,Offset 为 0x30(十进制为 48)。
根据 Set 定位到 Set = 0 的两个缓存行,对比 Tag 并且检查 Valid 是否为 1(表示该缓存行有效),即可进一步根据 Offset 进行访问。在本例中取出的 4 字节字为 23。如果在寻址过程中,虽然 Set 和 Tag 都匹配上了,但是 Valid 为 0,那么该缓存行是无效的。此时,该物理地址的数据将通过内存进行访问。
由于软件的运行通常具有局部性(包括时间局部性和空间局部性),因此缓存能够有效提升 CPU 访问物理内存数据的性能;另外,操作系统和应用程序也可以根据 CPU 缓存的特点对代码实施优化,从而更好地利用缓存以提升性能。
设备与中断
内存映射输入输出
内存映射输入输出(Memory-Mapped I/O, MMIO)是一种常见的 CPU 控制和访问设备的方式。MMIO 的原理是:把输入输出设备和物理内存放到同一个地址空间,为设备内部的内存和寄存器也分配相应的地址。
轮询与中断
轮询是由CPU主动周期性查询外部设备状态的机制,适合低优先级、不频繁的事件处理,但效率较低,会消耗CPU资源。
中断是由外部设备主动触发信号中断CPU当前任务,转而执行中断服务程序,适合高优先级、高实时性的事件处理,效率较高,可避免CPU空转。
MMIO 使得 CPU 可以主动地访问设备,中断使得设备能够主动地通知 CPU,这两种机制是 CPU 与设备之间交互的重要方式。