跳到主要内容

中断和异常处理

中断和异常是处理器响应外部事件和内部错误的重要机制。本章详细介绍 RISC-V 的中断和异常处理机制。

中断与异常的区别

异常(Exception)

异常是处理器内部产生的同步事件:

  • 非法指令
  • 断点
  • 内存访问错误
  • 系统调用
  • 地址未对齐

异常是确定性的,相同的代码在相同条件下会产生相同的异常。

中断(Interrupt)

中断是外部产生的异步事件:

  • 定时器中断
  • 外部设备中断
  • 软件中断(核间通信)

中断是异步的,与当前执行的指令无关。

异常类型

指令相关异常

非法指令异常(Exception Code = 2)

当处理器遇到无法识别的指令时触发:

# 非法指令示例
.word 0x00000000 # 全零指令通常是非法的

断点异常(Exception Code = 3)

执行 ebreak 指令时触发:

ebreak              # 触发断点异常

内存访问异常

地址未对齐异常(Exception Code = 0, 4, 6)

访问未对齐的内存地址时触发:

# 加载地址未对齐
lw t0, 1(a0) # 如果 a0 不是 4 的倍数,触发异常

# 存储地址未对齐
sw t0, 2(a0) # 如果 a0 不是 4 的倍数,触发异常

访问错误异常(Exception Code = 1, 5, 7)

访问无效内存或权限不足时触发:

# 访问无效地址
li t0, 0xFFFFFFFF
lw t1, 0(t0) # 触发访问错误

系统调用异常

环境调用异常(Exception Code = 8, 9, 11)

执行 ecall 指令时触发:

# 用户模式系统调用
li a7, 64 # 系统调用号
ecall # 触发异常码 8

中断类型

软件中断

软件中断用于核间通信:

中断码名称描述
1SSI监督软件中断
3MSI机器软件中断

触发软件中断:

# 向另一个核心发送软件中断
# 需要写入对方的 msip 寄存器
li t0, 1
sw t0, 0(msip_addr)

定时器中断

定时器中断用于实现定时功能:

中断码名称描述
5STI监督定时器中断
7MTI机器定时器中断

设置定时器:

# 设置机器定时器
# mtime 是实时计数器,mtimecmp 是比较值
la t0, mtime
ld t1, 0(t0) # 读取当前时间
addi t1, t1, 1000 # 1000 个周期后触发
la t0, mtimecmp
sd t1, 0(t0) # 设置比较值

外部中断

外部中断来自外部设备:

中断码名称描述
9SEI监督外部中断
11MEI机器外部中断

外部中断通常由中断控制器(如 PLIC)管理。

中断优先级

当多个中断同时发生时,按照以下优先级处理:

  1. 外部中断 > 定时器中断 > 软件中断
  2. 机器模式 > 监督模式 > 用户模式

异常处理流程

异常进入

异常发生时,处理器自动执行以下操作:

  1. 保存返回地址
mepc = pc(或 sepc = pc)
  1. 记录异常原因
mcause = 异常码(或 scause = 异常码)
  1. 记录附加信息
mtval = 相关信息(或 stval = 相关信息)
  1. 保存特权模式
mstatus.MPP = 当前特权模式
  1. 禁用中断
mstatus.MIE = 0
  1. 跳转到处理程序
pc = mtvec(或 stvec)

异常处理程序

.section .text
.globl trap_entry

trap_entry:
# 分配栈空间
addi sp, sp, -256

# 保存通用寄存器
sw x1, 4(sp)
sw x5, 8(sp)
sw x6, 12(sp)
# ... 保存其他寄存器 ...

# 读取异常原因
csrr t0, mcause

# 判断中断还是异常
blt t0, zero, handle_interrupt

# 处理异常
handle_exception:
li t1, 2
beq t0, t1, illegal_instruction_handler
li t1, 8
beq t0, t1, ecall_handler
# ... 其他异常处理 ...

handle_interrupt:
andi t1, t0, 0xF
li t2, 7
beq t1, t2, timer_interrupt_handler
li t2, 11
beq t1, t2, external_interrupt_handler
# ... 其他中断处理 ...

trap_exit:
# 恢复通用寄存器
lw x1, 4(sp)
lw x5, 8(sp)
lw x6, 12(sp)
# ... 恢复其他寄存器 ...

# 释放栈空间
addi sp, sp, 256

# 返回
mret

异常返回

mret 或 sret 指令执行以下操作:

  1. 恢复特权模式
当前特权模式 = mstatus.MPP
  1. 恢复中断使能
mstatus.MIE = mstatus.MPIE
  1. 恢复程序计数器
pc = mepc(或 sepc)

中断嵌套

中断嵌套允许高优先级中断打断低优先级中断的处理。

使能中断嵌套

# 在异常处理程序中重新使能中断
trap_entry:
# ... 保存上下文 ...

# 使能中断嵌套
csrs mstatus, (1 << 3) # 设置 MIE

# ... 处理异常 ...

中断嵌套注意事项

  1. 每个中断处理程序需要独立的栈空间
  2. 需要正确保存和恢复上下文
  3. 需要合理设置中断优先级

向量中断

向量中断为不同的中断类型提供不同的处理入口。

配置向量中断

# 设置向量模式
la t0, trap_vector_table
ori t0, t0, 1 # MODE = 1(向量模式)
csrw mtvec, t0

向量表结构

基地址 + 0x00: 异常处理入口
基地址 + 0x04: 软件中断入口(保留)
基地址 + 0x08: 软件中断入口(保留)
基地址 + 0x0C: 机器软件中断入口
基地址 + 0x10: 软件中断入口(保留)
基地址 + 0x14: 软件中断入口(保留)
基地址 + 0x18: 监督定时器中断入口
基地址 + 0x1C: 软件中断入口(保留)
基地址 + 0x20: 软件中断入口(保留)
基地址 + 0x24: 机器定时器中断入口
基地址 + 0x28: 软件中断入口(保留)
基地址 + 0x2C: 软件中断入口(保留)
基地址 + 0x30: 监督外部中断入口
基地址 + 0x34: 软件中断入口(保留)
基地址 + 0x38: 软件中断入口(保留)
基地址 + 0x3C: 机器外部中断入口

定时器中断实现

定时器初始化

init_timer:
# 获取 mtime 和 mtimecmp 地址
la t0, mtime
la t1, mtimecmp

# 读取当前时间
ld t2, 0(t0)

# 设置下次中断时间(1秒后,假设时钟频率 10MHz)
li t3, 10000000
add t2, t2, t3
sd t2, 0(t1)

# 使能定时器中断
li t0, (1 << 7) # MTIE
csrs mie, t0

# 使能全局中断
li t0, (1 << 3) # MIE
csrs mstatus, t0

ret

定时器中断处理

timer_interrupt_handler:
# 重置定时器
la t0, mtime
la t1, mtimecmp
ld t2, 0(t0)
li t3, 10000000
add t2, t2, t3
sd t2, 0(t1)

# 执行定时任务
# ...

j trap_exit

外部中断处理

PLIC 初始化

init_plic:
# 设置中断优先级阈值
li t0, PLIC_PRIORITY_THRESHOLD
li t1, 0 # 接受所有优先级的中断
sw t1, 0(t0)

# 使能特定中断源
li t0, PLIC_ENABLE
li t1, 0xFFFFFFFF # 使能所有中断
sw t1, 0(t0)

# 使能外部中断
li t0, (1 << 11) # MEIE
csrs mie, t0

ret

外部中断处理

external_interrupt_handler:
# 读取中断 ID
li t0, PLIC_CLAIM
lw t1, 0(t0) # t1 = 中断 ID

# 根据中断 ID 处理
beqz t1, no_interrupt

# 调用对应的中断处理函数
slli t1, t1, 2 # t1 = ID * 4
la t2, irq_handler_table
add t2, t2, t1
lw t3, 0(t2)
jalr t3

# 完成中断处理
li t0, PLIC_CLAIM
sw t1, 0(t0) # 写入完成信号

j trap_exit

no_interrupt:
j trap_exit

系统调用实现

系统调用接口

# 系统调用约定
# a7: 系统调用号
# a0-a5: 参数
# a0: 返回值

# 系统调用号定义
.equ SYS_READ, 63
.equ SYS_WRITE, 64
.equ SYS_EXIT, 93

系统调用处理

ecall_handler:
# 读取系统调用号
mv t0, a7

# 根据系统调用号分发
li t1, SYS_READ
beq t0, t1, sys_read
li t1, SYS_WRITE
beq t0, t1, sys_write
li t1, SYS_EXIT
beq t0, t1, sys_exit

# 未知系统调用
li a0, -1
j ecall_return

sys_read:
# a0 = fd, a1 = buf, a2 = count
# ... 实现读取 ...
j ecall_return

sys_write:
# a0 = fd, a1 = buf, a2 = count
# ... 实现写入 ...
j ecall_return

sys_exit:
# a0 = exit code
# ... 实现退出 ...

ecall_return:
# 更新返回地址
csrr t0, mepc
addi t0, t0, 4
csrw mepc, t0

j trap_exit

小结

本章介绍了 RISC-V 中断和异常处理:

  • 异常类型:非法指令、断点、内存访问错误、系统调用
  • 中断类型:软件中断、定时器中断、外部中断
  • 异常处理流程:进入、处理、返回
  • 中断嵌套:高优先级中断打断低优先级
  • 向量中断:不同中断类型有不同入口
  • 定时器中断:实现定时功能
  • 外部中断:处理外部设备中断
  • 系统调用:用户模式请求内核服务

中断和异常处理是操作系统内核的核心功能。下一章将介绍内存管理。