hnuOS lab4
lab 4 异常处理
实验目的
中断、异常和陷阱指令是操作系统的基石,现代操作系统就是由中断驱动的。本实验和实验五的目的在于深刻理解中断的原理和机制,掌握CPU访问中断控制器的方法,掌握Arm体系结构的中断机制和规范,实现时钟中断服务和部分异常处理等
实验过程记录
前序知识提要
陷入操作系统
执行trap执行,出现异常,发生中断时都会陷入OS。
ARMv8 的中断和异常处理
[!NOTE] Title
查看技术参考手册了解架构-AArch 64 异常模型 — Learn the architecture - AArch64 Exception Model
ARMv8 架构定义了两种执行状态(Execution States),==AArch64 和 AArch32。==分别对应使用64位宽通用寄存器或32位宽通用寄存器的执行 1 。
上图所示为AArch64中的异常级别(Exception levels)的组织。可见AArch64中共有4个异常级别,分别为==EL0,EL1,EL2和EL3==。在AArch64中,Interrupt是Exception的子类型,称为异常。
异常类型
AArch64 中有四种类型的异常
了解架构-AArch 64 异常模型 — Learn the architecture - AArch64 Exception Model
- Sync(Synchronous exceptions,同步异常),在执行时触发的异常,例如在尝试访问不存在的内存地址时,如系统调用。
- IRQ (Interrupt requests,中断请求),由外部设备产生的中断
- FIQ (Fast Interrupt Requests,快速中断请求),类似于IRQ,但具有更高的优先级,因此 FIQ 中断服务程序不能被其他 IRQ 或 FIQ 中断。
- SError (System Error,系统错误),用于外部数据中止的异步中断
当异常发生时,处理器将执行与该异常对应的异常处理代码。在ARM架构中,这些异常处理代码将会被保存在内存的异常向量表中。每一个异常级别(EL0,EL1,EL2和EL3)都有其对应的异常向量表。需要注意的是,与x86等架构不同,该表包含的是要执行的指令,而不是函数地址
异常向量表
了解架构-AArch 64 异常模型 — Learn the architecture - AArch64 Exception Model
异常向量表的基地址由==VBAR_ELn==给出,然后每个表项都有一个从该基地址定义的偏移量。 每个表有16个表项,每个表项的大小为128(0x80)字节(32 条指令)。 该表实际上由4组,每组4个表项组成。 分别是:
- 发生于当前异常级别的异常且SPSel寄存器选择SP0 4 , Sync、IRQ、FIQ、SError对应的4个异常处理。
- 发生于当前异常级别的异常且SPSel寄存器选择SPx 4 , Sync、IRQ、FIQ、SError对应的4个异常处理。
- 发生于较低异常级别的异常且执行状态为AArch64, Sync、IRQ、FIQ、SError对应的4个异常处理。
- 发生于较低异常级别的异常且执行状态为AArch32, Sync、IRQ、FIQ、SError对应的4个异常处理。
以下为矢量表
实验过程
定义异常向量表
新建 src/bsp/prt_vector.S 文件,参照以上vector-table, 定义异常向量表如下:
1 |
|
- 向量表布局
ARMv8 的异常向量表包含 16 个条目,每个条目占用 128 字节(0x80),对应不同的异常类型和上下文状态。代码中通过.org
指令定位到每个条目的起始地址。 - 异常处理宏
每个条目通过 ==EXC_HANDLE
宏==跳转到统一的异常分发函数:-
EXC_HANDLE <num>, OsExcDispatch
将异常编号<num>
和跳转目标OsExcDispatch
传递给宏。 -
OsExcDispatchFromLowEl
特殊处理从低 EL(如用户态)触发的同步异常(如系统调用)。
-
在 prt_reset_vector.S 中的 OsEnterMain: 标号后加入代码
上下文的保存和恢复
==定义EXC_HANDLE宏==,主要作用是一发生异常就立即保存CPU寄存器的值,然后跳转到异常处理函数进行异常处理。
1 |
|
-
stp
(Store Pair) 指令用于同时存储两个寄存器到栈,[sp,#-16]!
表示:sp = sp - 16
(栈向下增长)- 存储
x1, x0
到[sp]
和[sp+8]
。
-
ldp
(Load Pair) 指令从栈中加载两个寄存器,[sp],#16
表示:- 从
[sp]
和[sp+8]
加载数据到寄存器。 sp = sp + 16
(栈指针恢复)。
- 从
-
xzr
(零寄存器) 用于占位,因为x31
是xzr
(写入无效)。
[!NOTE] Title
在 ARMv8-A (AArch64) 架构中,xzr
(Zero Register,零寄存器) 是一个特殊的寄存器,它的值始终为 0,并且任何写入操作对它都无效(即无法修改它的值)。
继续在 src/bsp/prt_vector.S 文件中实现异常处理函数,包括 OsExcDispatch 和 OsExcDispatchFromLowEl。
1 |
|
[!NOTE]
保存系统寄存器
-
esr_el1
:记录异常原因(如中断类型、系统调用号)。-
far_el1
:触发异常的访问地址(如页错误地址)。-
spsr_el1
:异常发生时的 CPU 状态(如 PSTATE)。-
elr_el1
:异常返回地址(即被中断的指令地址)。- 通过
stp
指令将这些寄存器压栈,形成 异常上下文结构体。调用 C 处理函数
-
x0
:传递异常类型(如0
=同步异常,1
=IRQ)。-
x1
:传递栈指针(指向保存的上下文),供 C 函数解析。-
bl OsExcHandleEntry
:跳转到 C 函数,实现具体逻辑(如中断处理、系统调用分发)。恢复系统寄存器
- 从栈中恢复
elr_el1
和spsr_el1
,确保异常返回后能继续执行原程序。-
dsb sy
+isb
:保证内存和指令同步,避免乱序执行问题。恢复通用寄存器并返回
-
RESTORE_EXC_REGS
:恢复所有通用寄存器(x0-x30)。-
eret
:返回到elr_el1
指向的地址,并恢复spsr_el1
中的 CPU 状态。
异常处理函数
新建 src/bsp/prt_exc.c 文件,实现实际的 OsExcHandleEntry 和 OsExcHandleFromLowElEntry 异常处理函数。
1 |
|
新建 src/bsp/os_exc_armv8.h 文件,定义 ExcRegInfo 结构。存储的就是刚刚压到栈里的那些值。
1 |
|
启用fpu实现系统调用
cpu启动后进入的是el1或以上级别
在 main 函数中我们首先返回到 EL0 级别,然后通过 SVC 调用一条系统调用.
从异常返回:eret指令
1 |
|
系统调用实现
在 src/bsp/prt_exc.c 修改 OsExcHandleFromLowElEntry 函数实现 1 条系统调用。
1 |
|
成功输出
作业
查找 启用FPU 前异常出现的位置和原因。禁用FPU后PRT_Printf工作不正常,需通过调试跟踪查看异常发生的位置和原因 elr_el1 esr_el1 寄存器
- 禁用fpu后进行调试
- 在main.c处打上断点逐步调试
- 可以看到,程序在 PRT_Printf函数内部的TryPrintf遇到异常,查找osVectorTable
- 查看异常向量表可知,这是一个同步异常,在使用SP_ELx时当前EL出现 异常,异常级别为非用户级
- 查看esr-el1,elr-el1有
[!NOTE]
ARMv8 异常处理简介 - 内核工匠 - 博客园
异常链接寄存器ELR(Exception Link Registers)包含异常返回地址。当处理器发生异常时,返回地址将保存在异常级别对应的ELR中。
异常综合表征寄存器ESR_ELn包含的异常信息用以异常处理程序确定异常原因。
ESR_ELn的BIT[31:26]指示处理程序执行对应的异常,查看Arm A-profile 架构寄存器 — Arm A-profile Architecture Registers有
我们可以知道这是由fpu引起的异常
ELR_EL1代表EL1出现异常时要返回的地址,查看一下
这告诉我们,发生异常时下一条待执行的指令:
指令 str q0, [sp, #80],将128位向量寄存器q0
的值保存到栈指针sp
偏移80字节的内存位置
也就是说,我们在这条指令出错,原因是我们禁用了fpu,CPU无法执行浮点或SIMD指令(如str q0
)。此时若程序尝试执行这类指令,会触发未定义指令异常(同步异常),表现为ESR_EL1
的EC字段指示非法操作。
ds
异常发生时,CPU会自动保存PC
到ELR_EL1
、状态到SPSR_EL1
,并跳转到异常向量表。ERET
指令会恢复这些寄存器值,返回用户态