2.2 特权级模型与中断
特权级模型
众所周知, 操作系统也是一个程序. 跟普通程序不同的是, 操作系统拥有一些特权操作, 这些操作普通程序想使用只能通过系统调用.
一般有四个异常级别.
EL0, 也叫用户态. 一般程序就运行在这个特权级
EL1, 也叫内核态. 操作系统就运行在这里
EL2, 虚拟机监控器, 与虚拟化相关
EL3, 安全监控器, 与 TrustZone 相关
当用户态的程序想要进行系统调用时, 就会触发异常. 相关寄存器会保存一些信息, 操作系统遇到异常就会来看看是怎么一回事. 如果是系统调用, 那程序可以进入内核态, 执行一些特权操作了.
那么这里就存在一个问题. 操作系统要怎么知道有没有异常? 是要时不时就检查一下异常相关的寄存器吗? 这不太可能, 如果这样做的话一段时间内只允许有一次异常, 效率太低了. 必须要有什么东西来通知一下操作系统.
中断
基本概念
通用概念
- 中断(interrupt)
外部 硬件 设备所产生的信号, 比如键盘, 鼠标等操作会引起中断, 这种中断是异步的, 也叫硬中断.
异步可以简单理解成不知道什么时候会发生, 同步就是知道什么时候会发生.
- 异常
通常是由软件的程序执行而产生的事件. 包括系统调用等. 这种是同步的, 是软中断的一种.
ARM 的中断(异步异常)
- 重置(RESET)
最高级别的异常, 用来执行代码初始化 CPU 核心, 由系统首次上电或控制软件, 看门狗触发.
- 中断(interrupt)
CPU 外部的信号触发, 打断当前执行. 如计时器, 键盘等.
ARM 的(同步)异常
- 中止(Abort)
失败的指令获取或者数据访问导致的.
- 异常产生指令
SVC(Supervisor Call)系统调用
HVC(Hypervisor Call)
SMC(Secure Model Call)
x86-64 术语
中断的产生
ARM 中断的分类
- IRQ
普通中断, 优先级低, 处理慢
- FIQ
快速中断, 优先级高, 处理快.
一次只能有一个 FIQ, 通常为可信任的中断源预留
- SError
系统错误, 原因难以定位, 较难处理的异常, 一般由异常中止导致的.
中断控制器 GIC
主要功能:
分发: 管理所有中断, 决定优先级, 路由
CPU 接口: 给每个 CPU 核由对应的接口
GIC 有这些状态
inactive 中断源没有触发
pending 中断源已经触发, 但 CPU 还没处理
active 中断源已经触发, CPU 正在处理
active and pending 同一种中断源, 其中一次中断正在被处理, 下一次中断在等待
异常处理
中断和异常发生时, 会将 IRQ, FIQ 信号发给 CPU. CPU 进入异常和中断处理, 会根据相关信息选择合适的 handler 进行处理.
在进入中断或异常时, CPU 需要先进行一些准备工作:
保存处理器状态, 方便完成中断/异常后恢复执行
准备好在高特权级下进行执行的环境
选择合适的异常处理器代码进行执行
保证用户态和内核态的隔离
处理时, 需要获取关于异常的信息.
返回时, 需要还原处理器的状态, 返回低特权级, 继续正常执行.
处理的流程
信息保存
异常或者中断发生时, 硬件会将相关信息和当前的状态保存在寄存器中.
处理器状态(PSTATE) -> SPSR_EL1
当前指令地址(PC) -> ELR_EL1
异常发生原因 根据异常的不同保存在不同的地方
这些寄存器都不能在用户态调用.
进入内核态
硬件会修改处理器状态, 进入内核态. 之后栈指针 SP 会自动换用 SP_EL1, 也就是内核栈. 这个内核栈通常比用户栈小很多.
寻找合适的 handler
操作系统有一些异常向量表, 每一个异常级别都有自己的异常向量表. 表项是异常向量, 是可以处理异常或者跳转到异常 handler 的小段汇编代码, 基地址会存在 VBAR_EL1 寄存器中.
操作系统会根据异常类型, 异常发生的特权级和处理器状态来选择合适的表项.
返回
跟普通的 ret 不同, 使用的是 eret. 这会恢复 PC 状态, 处理器状态和栈指针状态, 同时切换至用户态, 恢复执行.
x86-64 的中断/异常处理
- 进入异常
硬件会将上下文信息和错误码保存在内核栈中.
- 用异常向量表寻找 handler
不分级了, 异常向量表里存的是 handler 的地址.
- 使用 iret 返回