一、总论
这篇文章的目的是为了比较各个硬件架构之间的异同点,这是因为我的朋友提到了“硬件是围绕软件来设计的”的观点,所以我觉得许多硬件的差异如果进行对比,可以更好得了解软件需求,看清各式各样的硬件的设计意图。
但是这篇文章似乎因为选题过于宏大了,导致非常难以下笔,所以这篇文章应该是一个长期维护的文章。
维护考虑:
- 或许可以根据 Linux 的命名方式来区分一些概念?
二、异常处理
2.1 异常概念
Arm64
RISCV
RISCV 的异常分为同步异常和中断两种,可以根据 mcause
的最高位来区分。
RISCV 的同步异常分为 5 种:
- 访问错误异常:当物理内存的地址不支持访问类型时发生(例如尝试写入 ROM)。
- 断点异常:在执行
ebreak
指令,或者地址或数据与调试触发器匹配时发生。 - 环境调用异常:在执行
ecall
指令时发生。 - 非法指令异常:在译码阶段发现无效操作码时发生。
- 非对齐地址异常:在有效地址不能被访问大小整除时发生。
中断源分为 3 种:
- 软件:向内存映射寄存器中写入数来触发,由一个 hart 中断另一个 hart 。
- 时钟:当
hart
的时间比较器(一个名为mtimecmp
的内存映射寄存器)大于实时计数器mtime
时,会触发时钟中断。 - 外部:由平台级中断控制器(大多数外部设备连接到这个中断控制器)引发。
不过我也不知道为啥,这些种类并不会一一对应 mcause
的编码,而是一对多,总结如下:
2.2 相关寄存器
Arm64
RISCV
在 M-mode 下,一共有 8 个 CSR 是 RISCV 异常处理所必须的,如下表所示:
CSR | 功能 |
---|---|
mtvec(Machine Trap Vector) | 异常发生时处理器跳转到的地址 |
mepc(Machine Exception PC) | 发生异常的指令 PC |
mcause(Machine Exception Cause) | 异常发生的种类 |
mie(Machine Interrupt Enable) | 处理器目前能处理和忽略的中断 |
mip(Machine Interrupt Pending) | 处理器正在准备处理的中断 |
mtval(Machine Trap Value) | 保存附加信息,比如地址异常中出错的地址、发非法指令异常中指令本身 |
mscratch(Machine Scratch) | 暂时存放数据 |
mstatus | 运行状态 |
前面 6 个寄存器都专用于异常处理,mscratch
寄存器在异常处理中没有明确意义,软件开发者可以当成一个指针来使用它,比如说在 Exaros 中它用来保存 TrapFrame
,mstatus
是运行状态寄存器,它在异常处理中只有一些域发挥作用,如下所示:
MIE
: Machine Interrupt Enable 全局中断使能MPIE
: Previous Machine Interrupt Enable 在异常发生后保存MIE
的旧值
需要区分 mstatus
的 MIE
位和 mie
寄存器, MIE
只有一位,置 1 表示允许中断,mie
是一个完整的寄存器,其中每一位都对应一个中断的使能,比如说 mie[7]
对于 M 模式的时钟中断。而 mip
也是一个完整寄存器,每一位对应一个中断“当前是否待处理”。如果 mstatus.MIE & mie[7] & mip[7] == 1
,则处理器可以处理时钟中断。
mstatus
这些寄存器只有一个副本,那是不是意味着没法进行中断嵌套了呢?并不是的,只需要保存到栈上即可。
默认情况下,所有异常(无论在什么权限模式)都会被转发到 M 态。不过 M-mode 可以通过委托机制交给 S-mode
2.3 异常路由
Arm64
svc
supervisor call 用户进程请求 OS 服务hvc
hypervisor call 客户 OS 请求虚拟机监视器服务smc
Secure Monitor calleret
异常返回,从SPSR
中恢复PSTATE
RISCV
- 使用
mret
从 M-mode 返回。 - 使用
sret
从 S-mod 返回。 - 使用
ecall
请求服务
arm64 有多个请求指令,只有 1 个返回指令,RISCV 有多个返回指令,只有 1 个请求指令。arm64 返回的异常等级根据 SPSR
决定,RISCV 的请求等级默认是 M-mode, M-mode 可以委托给 S-mode 处理。
2.4 硬件机制
Arm64
RISCV
异常发生时,RISCV 的处理流程如下:
- 将异常指令的 PC 保存在
mepc
中,将 PC 设置成mtvec
- 根据异常来源设置
mcause
和mtval
- 将先前的
mstatus.MIE
值写入mstatus.PMIE
,将mstatus.MIE
置零来禁用中断。 - 将发生异常时的权限模式写入
mstatus.MPP
,将权限模式改为 M-mode
用 mret
返回时:
- 将
mepc
写入 PC - 将
mstatus.PMIE
写入mstatus.MIE
- 将
mstatus.MPP
设置成权限模式
三、权限
3.1 权限等级
Arm64
在 Arm64 中,具有 4 个异常等级(Exception Level),可以通过 CurrentEL
寄存器进行查询。如下所示:
等级 | 含义 |
---|---|
EL0 | 用户态,运行用户进程 |
EL1 | 内核态,运行操作系统 |
EL2 | 虚拟机监视器态(Hypervisor) |
EL3 | 安全监视器态(Secure Monitor) |
在 Arm64 中的异常等级更像是一种从增长式的设计,最开始有 El0 和 EL1 (这个在 arm32 中甚至有多种更加细分的模式),为了支持虚拟机,引入了 EL2 ,为了支持可信计算,引入了 EL3 。
软件栈结构如下:
Riscv
在 RSICV 中,具有 4 个特权模式
模式 | 含义 |
---|---|
U | User/Application Mode, 运行用户进程 |
S | Supervisor Mode, 运行操作系统 |
H | Hypervisor Mode, 运行虚拟机监视器 |
M | Machine Mode |
软件栈结构如下:
可以看到 RISCV 的软件栈的“层数”要更加多,这是因为它引入了 BI
层和 EE
层来增加可移植性。EE
是 Execute Environment 的意思,表示为上层软件提供服务的实体,而 BI
是 Binary Interface 的意思,表示服务具体的协议。举个例子,OS 是用户的 EE , 系统调用是 BI 。我们常说的 openSBI ,其实是是一种 SEE 和 SBI 的集合体。
软件栈最下层是 HAL(Hardware Abstract Layer) ,它是一个软件,提供了硬件平台的封装。
需要注意的是,一块 RISCV 的板子并不需要完全实现这四种特权模式以及对应的 ISA ,硬件厂商完全可以不实现 Hypervisor Mode 。这些特权模式共有 4 种组合模式,硬件厂商可以选择其中的一种来实现(必须实现 Machine Mode):
名称 | 组合 |
---|---|
Simple Embedded | M |
Secure Embedded | M + U |
Unix-like OS capable | M + S + U |
Virtual Machine | M + H + S + U |
这些特权等级并不平等,比如说 H-mode 这个特权等级,需要通过 H
拓展来获得(主要提供第二级地址翻译和保护),否则一般是没有的。S-mode 也必须通过 M-mode 的异常委托机制才能发挥作用,M-mode 具有一种“拥有全部硬件权限”的语义,而不是像 Arm64 的 EL3 一样只是安全世界的一个守门人。将原本的 M
变成 M + U
似乎只是不信任的软件可以被隔离,提高系统的安全性,而将 M + U
编程 M + S + U
,似乎只是希望借用 S-mode 下虚拟内存功能,这是 M-mode 所不具备的。
我个人感觉因为 RISCV 的诞生比较靠后,所以可能在设计之初就对市场里的需求有一个总体的把握,不像 X86 或者 ARM 一样需要一步步迭代。此外 RISCV 在一开始就不是一种增量一体式的 ISA 设计,而是一个模块化的 ISA ,所以对于各种硬件功能的解耦更加彻底。
3.2 特权指令
Arm64
Arm64 的特权指令非常好记,系统寄存器写指令是 move system from register ,就是那种谁是 dst ,是在前的汇编常见风格。
msr csr, r
系统寄存器读指令是 move register from system
mrs r, csr
RISCV
RISCV 的系统寄存器被称为 CSR(Control and Status Register, 控制和状态寄存器) 。RISCV 的基础指令都具有 3 个操作数,而不是像 Arm64 一样的 2 个。
csrrw
(CSR read and write):读写,csr
中的值写入rd
,rs1
的值写入csr
。csrrs
(CSR read and set):读并置位,csr
中的值写入rd
,rs1
的值或上csr
中的值再写入csr
。csrrc
(CSR read and clear):读并清除,csr
中的值读入rd
,根据rs1
的值对csr
中的值按位清零再写入csr
中。
csrrwi
、csrrsi
、csrrci
分别于 csrrw
、csrrs
、csrrc
相似,除了它们是使用一个处于 rs1
字段的、零扩展到 XLEN 位的 5 位立即数。
我们常用的系统寄存器读指令 csrr
,系统寄存器写指令 csrw
,系统寄存器清空指令 csrc
和系统寄存器置位指令 csrs
都是伪指令,如下所示:
csrr rd, csr // csrrs rd, csr, zero
csrw csr, rs1 // csrrw zero, csr, rs1
csrc csr, rs1 // csrrc zero, csr, rs1
csrs csr, rs1 // csrrs zero, csr, rs1
3.3 运行状态
Arm64
不能认为 SPSR
和 PSTATE
等价,就像不能认为 Trapframe
和 x0, x1, x2, ..., sp, pc
等价。
RISCV
在某种简单的实现中,sstatus
就是 mstatus
的一部分,而不是完全不同的 banked register。从这幅图可以看出些许端倪。
四、指令集
4.1 GPR
4.2 指令
五、内存管理
5.1 内存同步
5.2 页表基址
Arm64
用户使用 ttbr0
寄存器,而内核使用 ttbr1
寄存器。
RISCV
用户和内核都使用 satp
寄存器。