02 计算机体系结构
计算机体系结构
什么是体系结构
在讨论计算机设计时,有几个概念经常被混在一起。实际上,它们对应的是从不同抽象层次、不同视角去看同一台计算机。自上而下来看,可以大致分为以下几个层级。
1.体系结构(Architecture)/ 指令集架构(ISA)
- 体系结构 = 软件眼里的CPU
- 这一层是从软件视角来看计算机,ISA的内容包括:
- 指令集:属于RISC还是CISC、支持的指令(如
ADD、MOV)、指令格式、操作数类型 - 寄存器结构:通用寄存器、状态寄存器(如
x86的EFLAGS、ARM的CPSR),各寄存器宽度 - 内存管理:虚拟内存映射,地址空间、对齐要求、内存访问语义、字节序
- 中断与异常模型:异常类型(如缺页、非法指令)、处理流程
- 特权级别:定义特权模式(如
x86的Ring 0-3、ARM的EL0-EL3) - 可选拓展:SIMD、虚拟化、DSP…
- 指令集:属于RISC还是CISC、支持的指令(如
- 至于硬件内部是顺序执行还是乱序执行、Cache多大,这层一概不管,只要对外行为不变就行
- 常见ISA:x86_64、ARMv7a、ARMv7m…
RISC-V、ARM其实都是ISA的统称,具体的ISA应该是RV32IA、ARMv7-M这样的
- ISA的宽度指的是CPU中通用寄存器的宽度(二进制的位数),这决定了寻址范围的大小,以及数据运算的能力。这与指令的宽度不同,比如RISCV架构CPU有32位也有64位,但指令的宽度都是32位。
2.组成原理 / 微架构(MicroArchitecture)
微架构 = CPU里面怎样组织效率更高
这一层关注的计算机的内部的组织方案,ISA定义了应该支持的功能后,硬件内部该怎么拆模块、怎么并行、怎么减少等待…微架构从硬件组织和性能优化的角度出发,讨论下述内容:
- 流水线设计:级数、超标量、乱序执行、分支预测等
- 寄存器重命名与调度机制
- Cache与TLB的大小、替换策略、全相连还是组相连
- 访存路径与一致性策略等
这一层已经深入到硬件内部结构,但仍然停留在架构和机制层面,而不是具体电路实现
修改微架构层级的设计,不应该影响程序运行的结果,只应该影响其效率。比如同样的代码可以在ARMv8A的CortexA72,CortexA58上运行,但是换到RISCV ISA的CPU上,肯定没法直接运行了
常见微架构:ARM的Cortex系列,玄铁做的C906…
3.数字电路 / 数字IC前端设计(RTL层)
RTL = 把设计的模块用数字电路实现
这一层从电路实现的角度出发,把微架构层的设计转化为可综合、可验证的硬件描述。关注点包括模块划分、时序设计、状态机、寄存器与组合逻辑的组织方式,以及使用VHDL等硬件描述语言来实现具体协议和模块,例如AMBA总线接口、Cache控制逻辑、一致性协议等
这一层的目标是如何设计电路,各条线该怎么连接,而不是再去讨论体系结构层面的取舍
一般买IP核,比如SoC厂买ARM的Cortex内核,最多都只是到这一层级,后端布局布线那些的还是得自己做
4.IC后端、工艺
- 这一层进一步把RTL实现的电路映射到具体芯片上,涉及综合、布局布线、时序收敛、功耗与面积优化以及制造工艺节点的限制。这一层已经完全脱离软件和体系结构语义,关注的是物理世界中的可制造性和可靠性问题
下面举个例子,从实现虚拟内存的角度,来看看不同层级应该做什么事情
ISA层级
- 定义虚拟内存对软件呈现的模型和语义,包括是否支持分页、页大小、页表格式、相关寄存器和指令、以及在何种条件下触发异常。该层只关心“程序看到什么行为”,而不关心硬件如何加速这些操作。
- 示例:ARMv8-A 定义 48 位虚拟地址和四级页表;RISC-V Sv39 定义三级页表结构。
微架构层级
- 在满足ISA语义的前提下,决定地址转换的具体硬件组织和性能优化策略,例如TLB的层级与结构、页表遍历缓存、是否支持并行查表,以及MMU如何与流水线和Load/Store单元协同工作。这一层是厂商进行性能和功耗差异化的核心
- 示例:Intel Skylake 采用多级TLB和硬件页表遍历器;Cortex-A72支持大页和页表缓存以减少TLB miss
SoC系统设计层级
- 关注地址转换在整个系统中的作用,包括转换结果如何影响片上互连和外设访问,以及如何与安全和虚拟化机制协同,例如TrustZone、二阶段地址转换和IOMMU。这一层通常涉及CPU核之外的系统组件
指令系统
指令集
定义:CPU能够执行的所有机器指令的集合,是软件与硬件交互的基础规范。例如:x86支持复杂指令(如MMX、AVX),ARM支持精简指令(Thumb-2)
分类:
CISC(复杂指令集):针对每一种功能都实现特定的指令,导致指令的数量较多,但生成的程序长度较短
RISC(精简指令集):只实现基本的指令,复杂的指令由基本指令组成,这导致指令的数目较少,但生成的程序长度较长
机器指令、汇编、编程语言的区别
指令集:**一个CPU真正支持的所有操作的==集合==**。它定义了 CPU 能够执行的指令类型、每条指令的操作码及其格式、操作数类型等。不同架构的CPU(x86、ARM、MIPS等)的主要区别之一就是指令集不同
机器指令:机器指令是CPU可以直接运行的二进制代码,一条机器指令就是指令集中的一条元素,对应CPU支持的特定操作,例如数据传送、算术运算、逻辑操作、控制流等
汇编语言:汇编语言是对机器指令的一种封装,使得用户可读(机器指令是二进制的,用户不可读),汇编是个动词,指的是将汇编语言转成CPU可以运行的机器指令。汇编这个过程类似编译,也需要一个程序(汇编程序)来完成,汇编相较于编译更为简单,通常只涉及将汇编指令映射为机器码。
由此可见汇编不具有跨平台的特性,在X86架构的CPU写的汇编,拿到ARM架构的CPU大概率不可用,因为指令集都不一样
汇编中指令和伪指令的区别
- 指令:对应于CPU支持的机器指令,汇编时直接映射为机器码
- 伪指令:辅助汇编程序编写和组织代码的数据定义和控制指令,不对应于CPU支持的机器指令,汇编时被转换为几条机器码
中断与异常
现在的SoC或者MCU内,都会有中断控制器,它们负责接收、筛选和派发中断给CPU。这个中断控制器可能被集成在CPU内核内(比如NVIC),也可能在CPU外部作为一个独立的IP核(比如GIC、PLIC)
中断控制器有点类似CPU内核,也是遵循“公开的规范 + 私有的实现”的形式
我们之前经常看到的被ARMv7-M使用的NVIC中断控制器,ARMv7-A使用的GIC中断控制器,其实都是由ARM提出的一个==规范==(例如GICv1, v2, v3, v4)
这份规范详细规定了:
- 功能模型:中断的生命周期(产生、分发、优先级仲裁、应答、结束)
- 编程接口:软件(操作系统、驱动程序)如何与GIC交互。这包括:
- 寄存器接口:定义了控制寄存器组(如GICD、GICC等)的功能、地址映射和访问方式
- 中断类型:SGI(软件生成中断)、PPI(私有外设中断)、SPI(共享外设中断)等的定义和行为
- 中断状态机:每个中断所处的状态( inactive, pending, active, active and pending)
- 兼容性要求:确保任何遵循此规范的GIC实现,都能被同一版本的软件驱动正确操作
从程序员的角度来看,只要2个SoC用的都是GIC标准,那么对其寄存器地址、优先级配置方法、中断开关流程的配置应该都是一样的,但是硬件上大概率是不一样的
各芯片厂商在获得ARM的授权后,会根据这份规范,用自己的方式设计出具体的GIC硬件电路,比如:
- Nvidia Tegra X1实现了 GIC-400(基于GICv2架构)
- Qualcomm Snapdragon 8 Gen 2 手机芯片实现了基于 GICv3 或 GICv4 架构的自研GIC
下面来分析几个不同的ISA,体会一下ISA到底包含哪些内容
ARMv7-A
ARMv7 ISA其实有多个不同的版本
| ARMv7-A | ARMv7-R | ARMv7-M | |
|---|---|---|---|
| 应用场景 | 应用处理器,高性能 OS | 实时控制 | 微控制器,低功耗 |
| MMU/MPU | 有 MMU | MPU | MPU |
| 指令集 | ARM + Thumb | ARM + Thumb | Thumb / Thumb-2 |
| 常见 CPU 核心 | Cortex-A8/A9/A53 | Cortex-R4/R5 | Cortex-M0/M3/M4 |
之前用的ARM Cortex A7的CPU内核的指令集架构是ARMv7A,现在对该ISA进行分析
特权等级
与RISCV的特权等级单一分层不同,ARMv7A使用了两层分层模型(特权等级+处理器模式)来设计
具体地,包含7种工作模式(分为2种特权等级),每个特权等级对应不同的处理器工作模式:
- 非特权等级(Unprivileged):仅
User模式 - 特权等级(Privileged):所有其他模式
| 模式 | 说明 | 特权级别 | 典型用途 |
|---|---|---|---|
| User (USR) | 普通用户模式 | 用户 | 应用程序执行 |
| FIQ (Fast Interrupt) | 快速中断模式 | 特权 | 高速中断处理 |
| IRQ (Interrupt) | 普通中断模式 | 特权 | 中断处理 |
| Supervisor (SVC) | 监控模式 | 特权 | OS 内核、系统调用 |
| Abort (ABT) | 内存访问异常(预取/数据)模式 | 特权 | 异常处理 |
| Undefined (UND) | 未定义指令模式 | 特权 | 异常处理 |
| System (SYS) | 内核模式,运行在特权级别的用户模式 | 特权 | 内核线程运行 |
小技巧记忆:
- User = 应用程序执行
- FIQ/IRQ = 中断处理
- SVC/System = OS 内核
- Abort/Undefined = 异常处理
User:用户程序的工作模式,运行在操作系统的用户态,所以没有权限操作硬件资源
- 无法直接切换到别的模式,如果想要访问硬件或者切换到别的模式,只能通过软中断(SWI指令)或者异常
IRQ:普通中断模式,用于处理一般的中断请求,延迟稍高于 FIQ
- R8–R12 与其他模式共享,需要保存/恢复通用寄存器
FIQ:高速中断,用于对延迟敏感的场景,优先级高于IRQ
- 拥有独立的 R8–R14,可以快速响应,避免保存/恢复通用寄存器
GIC内部可以把中断标记成IRQ或FIQ再发给CPU,一般只有Secure World或高优先级中断用的是FIQ。Linux内核通常不使用FIQ模式,直接用IRQ模式处理
Abort:用于异常处理,比如访问非法地址或页错误
- 发生内存访问异常时(Prefetch Abort / Data Abort),CPU切换到该模式
Undefined:未定义指令异常
- 处理未定义指令时,CPU切换到该模式
SVC:用于执行系统调用
CPU上电时进入的默认模式
由软件中断指令
SVC触发
System:用于内核线程的执行
- 和 User 模式共享寄存器(R0–R12)
- 不会自动保存SPSR寄存器
- 不需要用
SVC指令触发,当OS调度到一个内核线程时,进入该模式
工作模式切换
CPU的工作模式(特权等级)主要通过以下方式==切换==:
- 中断/异常:
- 发生中断/异常时,硬件自动切换到对应模式
- 中断/异常返回时,恢复原模式
- 软件触发:通过
SVC #imm汇编指令(#imm是立即数,可用作系统调用编号),CPU自动切到SVC模式。类似RISCV的ecall
寄存器

上面带三角的寄存器,在CPU不同的模式有不同的作用
通用寄存器
一共有13 + 5 + 7 + 7 +1 = 33个
R0~R15都属于通用寄存器,它们又可分为以下几类:
- R0–R12:普通寄存器,大部分模式共享,除了FIQ有专用的(R8~R12)
- 未被用于特殊用途,一般就用来暂存数据、传递函数参数、返回值、返回地址等
- 发生函数跳转时,会造成此类寄存器数据的破坏,因此需要保存当前的数据到栈空间中,即==保存上下文==,实际上就是在进程的栈区里创建一个栈帧保存当前各个寄存器的值
- R13 (SP):每种模式有独立的堆栈指针
- R14 (LR):相当于RISCV的
ra,发生调转时,保存跳转前的pc. 每种模式有独立的链接寄存器 - R15 (PC):程序计数器,全局共享
程序状态寄存器
一共1 + 6 = 7个
- CPSR(Current Program Status Register):当前程序状态寄存器

比较重要的功能有:
- 中断的开关
- 工作模式的设置
- 指令模型
- SPSR(Saved Program Status Register):备份的程序状态寄存器
- 发生中断/异常,会在目标模式的SPSR寄存器里备份CPSR的值
- 异常返回时,CPU用当前模式的目标模式SPSR 恢复CPSR
- ==每个特权模式都有独立==的SPSR寄存器,所以一共有6个(User无)
系统控制寄存器
| 寄存器 | 功能 |
|---|---|
| SCTLR | 系统控制寄存器,控制 MMU、Cache、分支预测等 |
| TTBR0 / TTBR1 | 页表基址寄存器(低地址 / 高地址空间) |
| DACR | 域访问控制寄存器,控制 domain 权限 |
| DFSR / IFSR | 数据/指令访问异常状态寄存器 |
| DFAR / IFAR | 数据/指令访问异常地址寄存器 |
| CPACR | 协处理器访问控制寄存器 |
| 其他 | TLB、ASID、MPIDR 等多种辅助寄存器 |
协处理器寄存器
- ARMv7-A支持协处理器(CP10/CP11 常用于浮点单元或 NEON)
- VFP / NEON寄存器组:
- S0–S31(单精度浮点)
- D0–D31(双精度浮点,S寄存器别名)
- 通过VMRS / VMSR / VLDR / VSTR指令访问
异常与中断
异常的概念:由CPU执行指令所导致的原来运行的程序的终止
异常类型
不同的异常源会使CPU进入不同的工作模式
| 异常类型 | CPU 模式 | 触发源(Source) | 特征/用途 |
|---|---|---|---|
| Reset | SVC / Supervisor | 上电或复位 | 初始化系统,CPU启动 |
| Undefined Instruction | UND | 执行未定义的指令 | 捕获非法指令异常 |
| Software Interrupt (SVC指令) | SVC | 执行 SVC 指令 | 用户程序请求系统调用 |
| Prefetch Abort | ABT | 指令预取错误(非法地址或权限) | 内存访问异常 |
| Data Abort | ABT | 数据访问错误(非法地址或权限) | 内存访问异常 |
| IRQ | IRQ | 外设普通中断 | 中断处理,一般优先级低 |
| FIQ | FIQ | 外设快速中断 | 高速中断,优先级高 |
异常处理流程
1.异常发生:CPU 捕捉到异常源(IRQ、FIQ、SVC、Abort、Undefined、Reset 等)
2.中断屏蔽检查:
- 如果是 IRQ/ FIQ:
- CPU 检查 CPSR.I / CPSR.F 位 。如果屏蔽位已置位,则不响应该中断
3.CPU自动切换到对应的工作模式
4.硬件自动操作部分寄存器:
- 将当前LR保存到对应工作模式的LR
- 将当前CPSR保存到对应工作模式的SPSR
- 交换当前SP和对应对应工作模式的SP
- PC跳转到异常向量表对应的位置

5.执行异常处理程序:
手动保存通用寄存器:将R0–R12
PUSH到当前模式的栈执行中断/异常处理逻辑(具体实现与OS相关):
普通IRQ/FIQ:读取外设中断号(GIC/中断控制器寄存器),分发给对应ISR
SVC:根据
SVC #imm中的立即数查系统调用表,调用内核服务Abort(缺页/存取异常):
访问系统控制寄存器:
DFSR/IFSR(Fault Status Register):错误类型
DFAR/IFAR(Fault Address Register):出错虚拟地址
内核根据异常类型决定修复(如加载缺页)还是杀死进程
Undefined:通常触发内核信号,可能是非法指令
可能需要的系统寄存器操作
- 内存管理相关:切换TTBR0/TTBR1(换页表)、失效TLB
- Cache操作:清除/失效Cache(通过CP15)
- 控制位修改:如修改SCTLR(开关MMU、Cache、对齐检查等)
- 异常信息获取:访问DFSR/IFSR、DFAR/IFAR
6.异常返回
- 软件手动恢复通用寄存器R0~R12
- 执行返回指令,如:
1 | MOVS PC, SPSR_svc ; 将 SPSR_svc 恢复到 CPSR,并跳回异常前的 PC |
- 硬件自动恢复CPSR、PC、SP寄存器,恢复到User模式
一图流:
内存管理
- 地址空间:ARMv7-A是32位的架构,所以最大4GB地址空间
- MMU:由系统控制寄存器中的SCTLR.M来控制MMU的开关
- 页表:
- 默认2级页表,由页表基址寄存器TTBR0和TTBR1存储页表基址
- 页表项定义了AP、XN等位进行权限的控制,定义了TEX、C、B等位来表示这块内存是普通内存还是外设的映射(PTE的内容原来是ISA定义的,不同的ISA还不一样)
- TLB:由专用的系统控制寄存器来控制,提供了刷新某页和全部TLB的指令
- Cache:由专用的系统控制寄存器来控制,提供了失效所有Cache等指令
- 异常相关:当内存访问错误时,ARMv7-A会触发Abort异常,并把原因和出错地址保存到DFSR、IFSR等寄存器
ARMv7-M
特权等级
ARMv7M同样使用了两层分层模型(特权等级+处理器模式)来设计
具体地,包含2种工作模式,但是同一个工作模式可以示特权级别的,也可是非特权级别的:
| 特性 | Thread mode | Handler mode |
|---|---|---|
| 特权级 | Privileged 或 Unprivileged | Privileged |
| 栈指针 | MSP或PSP(由CONTROL寄存器控制) | MSP |
| 进入方式 | 复位、异常返回 | 异常/中断 |
| 特权切换 | 可主动降级到Unprivileged | 不可降级,永远特权 |
| 典型用途 | RTOS线程 | 中断/异常处理 |
不同特权级别的区别:
Privileged(特权级)
可以访问所有指令和系统控制寄存器(例如 MPU、NVIC、SCB 等)
可以访问所有内存区域(除非 MPU 限制)
可以选择使用MSP或PSP作为栈指针
Unprivileged(非特权级)
被限制访问系统控制寄存器
内存访问受MPU限制
只能使用PSP作为栈指针
不同工作模式的区别:
Thread mode
异常处理程序之外的运行模式(比如用户应用或OS线程)
可以运行在特权级或非特权级
栈指针可以选择MSP或PSP(由
CONTROL[1]决定)系统复位时默认是该模式,且处于特权级,使用MSP
如果是裸机程序,就会一直在特权级+MSP下运行,但如果用RTOS,会把用户线程降级到非特权级+PSP,来进行隔离
Handler mode
处理异常/中断时进入的模式
永远运行在特权级
栈指针固定使用MSP(不受CONTROL寄存器影响)
RTOS的任务调度器运行在特权级 + MSP下,因为任务调度是靠SysTick/PendSV异常触发的
工作模式的切换
| 场景 | 切换方式 | 结果 |
|---|---|---|
| Thread → Handler | 发生异常/中断 | 自动切换,Handler mode + Privileged + MSP |
| Handler → Thread | 异常返回(EXC_RETURN) | 自动切换回 Thread mode(PSP 或 MSP) |
特权等级的切换
Thread mode 本身可以运行在不同的特权等级,可以通过CONTROL寄存器切换:
CONTROL[0] = 0→ PrivilegedCONTROL[0] = 1→ UnprivilegedCONTROL[1] = 0→ Thread 使用 MSPCONTROL[1] = 1→ Thread 使用 PSP
| 场景 | 切换方式 | 结果 |
|---|---|---|
| Thread (Privileged → Unprivileged) | MSR CONTROL, Rn |
立即生效 |
| Thread (Unprivileged → Privileged) | 触发异常,再在 Handler 里改 CONTROL | 通过异常回到特权级 |
注意:
- 从 Privileged → Unprivileged 可以直接改 CONTROL
- 从 Unprivileged → Privileged不行,只能通过触发异常(进入Handler mode),然后在异常处理程序里再改回去
寄存器
通用寄存器
R0~R15都属于通用寄存器,它们又可分为以下几类:
- R0–R12:通用工作寄存器,用于参数传递、临时变量保存
- R13 (SP):栈指针,有MSP (Main SP)和 PSP (Process SP)两个实现,具体使用哪个由
CONTROL[1]决定 - R14 (LR):相当于RISCV的
ra,发生调转时,保存跳转前的pc. 每种模式有独立的链接寄存器 - R15 (PC):程序计数器,全局共享
程序状态寄存器
xPSR (Program Status Register),类似ARMv7-A的CPSR寄存器,但他做了精简,有以下字段:
- APSR (Application PSR):条件码标志(N, Z, C, V, Q)
- IPSR (Interrupt PSR):当前异常号(0 = Thread mode)
- EPSR (Execution PSR):包含 Thumb 状态位 T,IT 块状态
控制/配置寄存器
CONTROL
- 用于控制Thread mode下的特权级别和栈指针的选择,与Handler mode无关
PRIMASK
1 = 屏蔽所有可屏蔽异常(除了 NMI 和 HardFault),0 = 正常
FAULTMASK
1 = 屏蔽所有异常(包括 HardFault),仅 NMI 能打断一般用于紧急临界区(比如出错恢复)
BASEPRI
存放一个优先级阈值
只允许比该值优先级更高的中断被响应
常用于 RTOS 的“临界区实现”
异常相关寄存器
在 System Control Block (SCB) 中,定义了一系列只读/可写寄存器:
- ICSR (Interrupt Control and State Register):显示当前异常号,支持触发 PendSV、SysTick 等
- AIRCR (Application Interrupt and Reset Control Register):系统复位、优先级分组配置
- SCR (System Control Register):睡眠模式控制
- SHCSR (System Handler Control and State Register):配置/查询系统异常状态
- CFSR (Configurable Fault Status Register):详细错误状态(分为 MemManage/Bus/Usage Fault)
- HFSR (HardFault Status Register):HardFault 错误源
- MMFAR/ BFAR:内存/总线错误的出错地址
NVIC寄存器
- ISER / ICER:中断使能/清除
- ISPR / ICPR:中断挂起/清除
- IPR:中断优先级设置
异常与中断
ARMv7-M 的异常总数最多496 个:
前 16 个是系统异常(System Exceptions)(编号 1–15)
16 之后是外部中断(IRQ, Interrupt Requests),编号 16–(n-1),由 NVIC 管理
异常类型
1.系统异常(固定16个)
| 异常号 | 名称 | 优先级 | 说明 |
|---|---|---|---|
| 15 | Reset | 固定最高 | 上电或复位时进入,取向量表中的 MSP 初值和 Reset Handler 地址 |
| 14 | NMI (Non-Maskable Interrupt) | 次高,仅次于 Reset | 不可屏蔽,常用于紧急错误处理 |
| 13 | HardFault | 次高(仅次于 NMI) | 严重错误(比如没有启用的 Fault 或执行错误) |
| 12 | MemManage Fault | 可配置 | 内存保护单元(MPU)相关错误 |
| 11 | BusFault | 可配置 | 总线访问错误(如非法地址访问) |
| 10 | UsageFault | 可配置 | 指令执行错误(未定义指令、除 0、未对齐访问等) |
| 9-6 | 保留 | - | - |
| 5 | SVCall (Supervisor Call) | 可配置 | 通过 SVC 指令触发,常用于系统调用接口 |
| 4 | Debug Monitor | 可配置 | 调试相关异常 |
| 3 | 保留 | - | - |
| 2 | PendSV (Pendable Service Call) | 可配置 | 用于任务切换,RTOS 核心异常 |
| 1 | SysTick | 可配置 | 系统定时器中断,通常配合 RTOS 用于调度 |
- PendSV:可挂起系统调用
- 跟SVC很像,都是用于从用户态切换到内核态用的。但是相比于SVC的立即触发,PendSV延迟触发。通过
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;指令开启一个PendSV请求后,CPU会在空闲或当前中断完成后响应,并进入Hadler Mode,而不是立即响应 - ==引入目的==:在RTOS中,任务的调度一般在SysTick中断中被触发,但是如果直接在SysTick中断中执行上下文切换的话,存在以下问题:
- 高优先级中断的处理延迟:任务调度会消耗一定时间,如果在 SysTick中断中执行任务切换,系统可能无法及时处理其他高优先级中断,影响系统的响应性
- 实时性问题: 对于实时系统来说,任务切换在中断中执行可能会导致中断服务的执行时间增加,从而影响系统响应外部事件的能力
- 为了解决这些问题,PendSV 引入了 延迟任务调度的机制,确保任务切换不会影响中断服务例程的执行。通过 PendSV,任务切换操作被 延迟到当前中断处理完成后 执行。具体流程:
- SysTick(或其他中断)触发任务调度:当 SysTick 定时器触发中断时,系统会标记需要进行任务切换。但任务切换不会立即在 SysTick ISR 中执行
- PendSV 异常挂起:在 SysTick ISR 中,我们 设置 PendSV 异常挂起,而不是直接在 ISR 中执行任务调度。这样做的目的是把任务切换的操作 推迟到当前中断处理完成后,并确保高优先级中断不会被任务调度中断
- PendSV 执行任切换:当当前中断处理完成后,系统会自动进入PendSV ISR,执行任务调度操作,并完成上下文切换
- 跟SVC很像,都是用于从用户态切换到内核态用的。但是相比于SVC的立即触发,PendSV延迟触发。通过
中断到线程的上下文切换可以用下图表示:

- SVC
- 通过
SVC #imm指令可以触发一个软件中断,让CPU立即自动切换到Handler Mode,主要用于触发系统调用(内存分配、任务调度…)
- 通过
2.外部中断 (IRQ)
- 从异常号16 开始,每个外部中断对应NVIC 一个输入线
- ARMv7-M 支持最多 240 个外设中断(具体数量取决于实现)
- 这些中断对应 SoC 外设(UART、SPI、I2C、GPIO、DMA …)
优先级
1 | Reset > NMI > HardFault > 其他 Fault/系统异常 > 外部中断 |
异常处理流程
1.异常触发
2.CPU自动切换工作模式和特权等级:切到Handler mode + Privileged
3.硬件自动操作部分寄存器:压R0–R3, R12, LR, PC, xPSR 到栈,切换SP为MSP
4.跳转到异常向量表中的异常入口
5.软件执行中断服务程序
6.执行BX LR进行异常返回,LR 中的EXC_RETURN字段决定返回到什么工作模式
STM32的异常向量表
1 | __Vectors DCD __initial_sp ; Top of Stack |
__initial_sp、xxx_Handler就是各个异常的处理函数,DCD是一个伪指令,但也能完成跳转
内存管理
ARMv7-M 并没有完全实现 MMU(内存管理单元) 和 虚拟内存。它依赖于 MPU(内存保护单元) 来管理内存访问权限,保障系统的稳定性和安全性
MPU 是一个硬件模块,==用于对内存区域的访问进行权限控制==。它的主要目的是提高系统的安全性和可靠性,防止任务对内存的非法访问
1.MPU 的工作原理:
MPU 区域(Region):MPU 可以配置多个内存区域。每个区域有一个 起始地址、大小 和 访问权限。
MPU 区域的权限控制:
- 访问权限(AP, Access Permissions):用于控制内存区域的访问权限,常见的权限包括:
- 可读/可写
- 只读
- 执行禁止(XN,Execute Never)
- 特权级别:内存区域可以对 特权级(privileged)和 非特权级(unprivileged)的访问进行区分。
- 访问权限(AP, Access Permissions):用于控制内存区域的访问权限,常见的权限包括:
2.MPU 的作用:
- 通过定义区域,可以限制任务访问某些特定内存区域,防止非法访问。
- 防止堆栈溢出或访问越界,保护任务堆栈和数据区。
- 在某些情况下,MPU 可以防止 代码注入 或 执行恶意代码,提高系统安全性。
3.MPU 配置:
ARMv7-M 支持最多 8 个 MPU 区域,并通过控制寄存器进行配置。每个 MPU 区域由以下几个要素决定:
- 起始地址
- 大小(通常是 32 字节、64 字节、128 字节、256 字节等的 2 的幂)
- 访问权限(读取、写入、执行等)
- 访问类型(如代码区域、数据区域等)
MPU 配置的寄存器:
- MPU_CTRL:用于启用/禁用 MPU。
- MPU_RNR:选择当前操作的 MPU 区域。
- MPU_RBAR:配置 MPU 区域的起始地址。
- MPU_RASR:配置 MPU 区域的大小、权限、执行等设置。
面试题
1.中断栈和任务栈有什么区别,保存在哪里
- 在 Cortex-M 内核上,所有中断共享一个中断栈(MSP),进入中断时硬件会自动保存部分寄存器;而每个任务有独立的任务栈,由 RTOS 管理,用来保存任务上下文和函数调用现场。任务切换时,调度器会把寄存器现场压入任务栈,并在切回时恢复
2.要实现任务调度,可以只有systick中断,不用pendsv吗?pendsv有什么优势
- 可以的,正常流程是在Systick Handler发起一个PendSV请求然后在中断结束后进行真正地任务调度
- 不用PendSV的话就直接在Systick Handler内部进行任务切换,这会带来以下问题:
- 增加中断的处理时间,影响实时性
- 如果某个任务自己触发了调度(比如调用
taskYIELD()),只能等下一个Systick到来时才能真正的切换
- PendSV的优势:
- 调度逻辑和时钟中断解耦,SysTick 只管触发调度请求,PendSV 专心做切换,更加灵活和高效
- 优先级一般设的比较低,这样它不会打断真正的中断而造成影响
- 可以暂时挂起延迟执行,等高优先级的中断运行完了才运行
- 不依赖SysTick,
taskYIELD()、信号量释放等情况,都可以设置PendSV的pending,从而触发调度
ARMv8-A
RISC-V
特权等级
RISCV包含4个特权等级
| 级别 | 编码 | 名称 |
|---|---|---|
| 0 | 00 | 用户/应用模式 (U, User/Application) |
| 1 | 01 | 监督模式 (S, Supervisor) |
| 2 | 10 | H, Hypervisor |
| 3 | 11 | 机器模式 (M, Machine) |
RISC-V 架构中,只有 M 模式是必须实现的,剩下的特权级则可以根据跑在 CPU 上应用的实际需求进行调整:
- 简单的嵌入式应用只需要实现 M 模式
- 带有一定保护能力的嵌入式系统(RTOS)需要实现 M/U 模式
- 复杂的多任务系统(如Linux)则需要实现 M/S/U 模式
- 到目前为止,(Hypervisor, H)模式的特权规范还没完全制定好
寄存器
基础整数寄存器
- RISC-V 基础指令集(RV32I/RV64I)包含32个通用寄存器和一个PC指针,每个寄存器的大小和处理器的位宽相关,可能为32/64/128位
- 每个寄存器在编程时有特定的用途和别名,由ABI决定
| 寄存器 | ABI 名称 | 用途 | 是否调用保存 |
|---|---|---|---|
x0 |
zero |
硬编码为 0,写入无效,读取始终返回 0 | - |
x1 |
ra |
返回地址(Return Address),用于函数返回(如 ret 指令) |
否 |
x2 |
sp |
栈指针(Stack Pointer),指向当前栈顶 | 是 |
x3 |
gp |
全局指针(Global Pointer),用于访问全局数据(可选优化) | - |
x4 |
tp |
线程指针(Thread Pointer),用于线程局部存储(TLS) | - |
x5-x7 |
t0-t2 |
临时寄存器,用于短期存储,函数调用后可能被覆盖 | 否 |
x8 |
s0/fp |
帧指针(Frame Pointer),用于调试或栈帧定位(可选) | 是 |
x9 |
s1 |
保存寄存器,函数调用后需恢复 | 是 |
x10-x11 |
a0-a1 |
函数参数/返回值,传递前两个参数或返回值 | 否 |
x12-x17 |
a2-a7 |
函数参数,传递第 3~8 个参数 | 否 |
x18-x27 |
s2-s11 |
保存寄存器,函数调用后需恢复 | 是 |
x28-x31 |
t3-t6 |
临时寄存器,函数调用后可能被覆盖 | 否 |
- “是否调用保存”指的是,发送函数调用时,是否会保存该寄存器的值,还是直接覆盖
浮点寄存器
如果实现 F 或 D 扩展(单精度/双精度浮点),RISC-V 提供32 个浮点寄存器 f0-f31,位宽由扩展决定:
F扩展:32 位(单精度)D扩展:64 位(双精度)
状态控制寄存器
不同的特权级别分别对应各自的一套状态控制寄存器CSRs,用于配置和监控 CPU 状态(==具体有哪些==,请看RISC-V ISA手册Part2的Table 2.2)
高级别的特权级别下可以访问低级别的CSR, 譬如 Machine Level 下可以访问 Supervisor/User Level 的 CSR,以此类推, 但反之不可以
RISC-V 定义了专门用于操作 CSR 的指令
RISC-V 定义了特定的指令可以用于在不同特权级别之间进行切换
异常与中断
异常类型
| 异常代码 | 助记符 | 描述 |
|---|---|---|
| 0 | 0 | 指令地址未对齐 (Instruction address misaligned) |
| 0 | 1 | 指令访问错误 (Instruction access fault) |
| 0 | 2 | 非法指令 (Illegal instruction) |
| 0 | 3 | 断点 (Breakpoint) |
| 0 | 4 | 加载地址未对齐 (Load address misaligned) |
| 0 | 5 | 加载访问错误 (Load access fault) |
| 0 | 6 | 存储/原子操作地址未对齐 (Store/AMO address misaligned) |
| 0 | 7 | 存储/原子操作访问错误 (Store/AMO access fault) |
| 0 | 8 | 用户模式系统调用 (Environment call from U-mode) |
| 0 | 9 | 监管模式系统调用 (Environment call from S-mode) |
| 0 | 11 | 机器模式系统调用 (Environment call from M-mode) |
| 0 | 12 | 指令页错误 (Instruction page fault) |
| 0 | 13 | 加载页错误 (Load page fault) |
| 0 | 15 | 存储/原子操作页错误 (Store/AMO page fault) |
相关寄存器
- MODE:当前CPU的特权模式
- SATP:当前MMU所使用页表的基址
- STVEC:内核中处理Trap的指令的地址
- SEPC:备份发生Trap时PC指针的值,结束时返回
- SSCRATCH:临时存储数据,通常用于上下文保存时,切换用户/内核栈,相当于一个cache
- SCAUSE:保存了trap是什么类型(中断/系统调用/异常)
- SSTATUS:中断使能控制;trap发生时的特权级保存;sret返回时的特权级设置
- 32个通用寄存器:作为上下文被保存
需要注意的是这些寄存器是S态的CSR寄存器。M态还有一套自己的CSR寄存器mcause,mtvec…
异常处理流程
1.Trap 发生
- 可能是 中断 (interrupt):外部中断、定时器中断、软件中断
- 也可能是 异常 (exception):非法指令、系统调用 (ECALL)、页错误、地址对齐错误等
2.中断屏蔽检查
- 先检查 mstatus 或 sstatus 里的中断使能位(如 MIE/SIE)
- 检查 mie/sie 寄存器的具体中断源使能
- 如果对应中断被屏蔽,则忽略
3.模式切换
- 如果启用了S,那么发生Trap时优先进入S Mode,否则直接进入M Mode
4.硬件自动操作寄存器(CSR 自动更新)
- 保存 PC:
- 当前 PC → mepc / sepc
- 保存异常原因:
- Trap 原因号 → mcause / scause
- 中断 vs 异常有标志位区分
- 保存出错地址(如果有):
- mtval / stval ← 出错的虚拟地址 / 指令字
- 更新控制寄存器:
- mstatus:保存中断使能位 MIE 到 MPIE,并关闭中断
- 切换到异常目标模式(M 模式或 S 模式)
- PC 跳转:
- PC ← mtvec / stvec(Trap 向量基址 + 偏移方式)
5.执行异常处理程序
- 手动保存通用寄存器
- RISC-V 不会自动保存 x1–x31,软件必须把需要用的寄存器保存到栈里
- 根据Trap类型执行逻辑
- 中断:查询 mcause/scause,确认是定时器中断、外部中断还是软件中断
- ECALL(系统调用):根据寄存器 a7(系统调用号)分发到内核服务
- 页错误(Page Fault):读取 stval/mtval 得到出错地址;OS 做缺页处理或杀进程
- 非法指令/对齐错误:根据策略决定信号/异常处理
- 可选的 CSR 操作
- 修改 satp(页表基址寄存器,切换地址空间)
- 清除/设置 mip/mie/sip/sie 中断位
- 可能会调整 mstatus/sstatus
6.异常返回
- 恢复之前保存的通用寄存器
- 执行异常返回指令:
- 从 M 模式返回:
MRET - 从 S 模式返回:
SRET
- 从 M 模式返回:
- 硬件自动完成:
- PC ← mepc/sepc(回到异常前指令)
- mstatus/sstatus 恢复中断使能(MPIE → MIE)
- 回到原模式(U/S)继续执行
内存管理
- 地址空间:
- RISC-V 支持多种虚拟内存方案:
- Sv32(32 位虚拟地址,用于 RV32,最大 4GB)
- Sv39(39 位虚拟地址,用于 RV64,最大 512GB)
- Sv48(48 位虚拟地址,用于 RV64,最大 256TB)
- 物理地址宽度由具体实现决定(通常 36~56 位)
- RISC-V 支持多种虚拟内存方案:
- MMU:
- 由 satp 寄存器控制,保存根页表基址和模式字段
satp.MODE=0时表示关闭 MMU(直接物理地址)
- 页表:
- 多级页表(Sv32 两级,Sv39/Sv48 三级/四级)
- 页表基地址由 satp.PPN 给出
- 页表项字段(ISA 定义):
- V/R/W/X/U/G/A/D 等位控制有效性、访问权限、执行权限、是否用户态可见、全局页、是否访问/修改
- 不同于 ARM,缓存/内存属性不是在页表项里定义,而是通过 PMA/PMP 来定义
- TLB:
- RISC-V 定义了
SFENCE.VMA指令用于 TLB 刷新,没有像 ARM 那样的 CP15 接口,而是通过这条指令通知硬件失效部分或全部 TLB
- RISC-V 定义了
- Cache:
- RISC-V 规范本身没有定义 Cache 控制指令
- Cache 行失效/清除等属于平台相关扩展(通常由实现或特权扩展提供)
- ISA 层面只保证内存一致性模型(RVWMO)
- 异常相关:
- 发生内存访问错误时会触发 Page Fault 异常(Instruction / Load / Store Page Fault)
- 出错虚拟地址保存在 stval/mtval寄存器,异常原因写入 scause/mcause 寄存器



