Skip to content

异常

1. 异常响应核心概念

  • 自陷指令 (Trap Instruction):
    用户程序执行自陷指令后会强制跳转到操作系统预先设定的异常入口地址,从而实现从用户程序到操作系统的受控切换。

  • 异常入口地址 (Exception Entry Point):
    操作系统在启动时设置的入口地址,用于接收所有的异常处理请求。

  • 中断/异常响应机制 (Interrupt/Exception Response Mechanism):
    整个过程由ISA规范定义。硬件会在异常发生时立即自动保存关键状态寄存器信息,然后跳转到异常入口地址。状态保存步骤由硬件自动完成,确保原子性和时序准确。


2. 各ISA的异常响应机制

x86

  • 自陷指令: int
  • 异常入口地址:门描述符 (Gate Descriptor)中断描述符表 (IDT) 指定
  • 响应流程:
  • CPU通过lidt加载IDTR,IDTR保存IDT的首地址和长度。
  • 根据int指令中指定的异常号,从IDT中索引出对应的门描述符,提取出异常入口地址 (OFFSET)。
  • eflags, cs, eip寄存器依次压进栈中保存状态。
  • 跳转至异常入口地址执行异常处理。
  • 返回: 使用iret指令,从栈中恢复eip, cs和eflags,返回到用户程序。

MIPS32

  • 自陷指令: syscall
  • 异常入口地址: 固定为 0x80000180
  • 状态寄存器:
  • EPC: 保存引发异常的PC
  • Status: 保存处理器运行状态
  • Cause: 存储异常原因(异常号)
  • 响应流程:
  • CPU将当前PC保存到EPC。
  • 将异常号写入Cause寄存器。
  • Status寄存器设置为内核态(以及其他标志)。
  • 跳转至0x80000180执行异常处理。
  • 返回: 使用eret指令恢复PC及相关状态,退出异常处理。

riscv32

  • 自陷指令: ecall
  • 异常入口地址: 存放在mtvec寄存器中
  • 状态寄存器:
  • mepc: 保存异常前的PC
  • mstatus: 保存处理器状态
  • mcause: 存储异常原因(异常号)
  • 响应流程:
  • CPU将当前PC写入mepc。
  • 异常号写入mcause。
  • 从mtvec中读取异常入口地址。
  • 跳转到异常入口地址,实现控制流切换。
  • 返回: 使用mret指令恢复PC,完成异常处理并返回用户程序。

3. 硬件状态保存与跳转

由于异常和中断发生时要求原子性以及极短的保存延时,仅依靠软件难以满足以下要求: - 原子性: 异常发生时需要立即保存所有关键信息,防止中断过程中状态变化。硬件能够在一条指令序列中原子性地保存所有寄存器。 - 时序要求: 异常触发时刻处于指令执行的确定边界,硬件可以确保精确保存PC、状态寄存器等内容。 - 保护机制: 特权级转换要求在进入内核之前保护操作系统内核代码和数据,软件手段难以在异常预处理阶段实现完全保护。

因此,在MIPS32和riscv32中,关键状态(如EPC/mepc、Status/mstatus、Cause/mcause)由硬件自动保存,而x86虽然通过栈保存异常号等信息,但也在硬件的控制下自动完成大部分状态保存工作。


4. 从异常处理返回

在异常处理完毕后,操作系统需要恢复原先断点的“正常”状态,再次使程序从中断前的位置继续执行: - x86: 通过iret指令将栈中保存的eip, cs和eflags恢复到CPU寄存器中。 - MIPS32: 使用eret指令,将EPC中的地址加载到PC,同时恢复status寄存器。 - riscv32: 使用mret指令,PC从mepc值恢复,退出异常处理状态。

在状态机视角下,异常引入了一个特殊的状态转移:当检测到错误或者自陷时,通过“raise_intr”指令(在硬件内部完成)将系统状态从用户执行状态S = {GPR, PC, SR}转移到异常处理状态,处理完毕后再完整恢复,确保异常处理过程对用户代码“透明”。


5. 从异常返回至用户程序:处理流程概览

  1. 自陷/异常触发: 用户程序调用如 ecall, syscall, 或 int 指令。
  2. 硬件响应: CPU自动保存当前PC和必要的状态寄存器,将异常号写入相应的寄存器,并跳转至从mtvec(或IDT, 固定地址)指定的异常入口地址。
  3. 异常入口的汇编处理: 汇编代码(如trap.S)在栈上分配上下文空间,保存所有通用寄存器、CSR寄存器(或CP0寄存器),并将栈指针作为上下文结构传递给C层处理函数(如__am_irq_handle)。
  4. C语言异常处理: __am_irq_handle()根据mcause/cause值识别异常类型(如EVENT_YIELD或系统调用),调用用户注册的异常处理函数(如simple_trap)。
  5. 异常后恢复: 异常处理完毕后,汇编代码恢复所有保存的寄存器和状态,并使用 iret/eret/mret 指令返回,继续执行原来中断发生后的代码。