RISC-V Trap / Syscall / Trampoline 机制完整解析


一、整体流程概览

1.1 总览

用户态通过 ecall 触发异常 → 硬件最小保存 → 跳转到 stvec → trampoline 完成上下文保存 + 切页表 → 进入内核 → 执行 syscall → 再通过 trampoline 返回用户态。


1.2 完整流程图(逻辑)

  1. 用户态执行 ecall
  2. 硬件:
    • 保存 sepc
    • 设置 scause
    • 修改 sstatus
    • 跳转 stvec
  3. trampoline (uservec):
    • 使用 sscratch
    • 保存寄存器到 trapframe
    • 切换到内核页表
    • 跳转 usertrap
  4. 内核处理 (usertrap):
    • 判断 syscall
    • 执行系统调用
  5. 返回 (usertrapret + userret):
    • 恢复寄存器
    • 切回用户页表
    • sret 返回

二、Trap 发生时的关键问题

2.1 Trap 刚发生时的状态

  • 权限:S-mode
  • 页表:仍是用户页表
  • 寄存器:全部是用户态数据
  • 栈:用户栈(不可用)

2.2 核心矛盾

系统已经进入内核态,但仍运行在用户地址空间中


三、sscratch 的设计哲学

3.1 问题:如何获取 trapframe?

要求:

  • 不能用寄存器(不可信)
  • 不能访问内存(未准备好)

3.2 解决方案:sscratch

  • 内核提前写入 trapframe 地址
  • 用户态无法访问

3.3 原子交换

csrrw a0, sscratch, a0

效果:

  • a0 = trapframe
  • sscratch = 原 a0

3.4 设计思想

  • 最小信任原则
  • 原子性
  • 零依赖启动

3.5 结论

sscratch 是 trap 入口唯一可信锚点


四、为什么必须有 trampoline

4.1 核心问题

trap 时:

  • 页表仍是用户页表
  • 内核代码不可见

4.2 如果直接跳内核

结果:

  • page fault
  • 无法执行

4.3 trampoline 的本质

一段在用户页表和内核页表中都映射的代码

4.4 它解决的三个问题

  1. 能执行第一条指令
  2. 能保存寄存器
  3. 切页表后还能继续执行

4.5 两阶段进入内核

阶段1:trampoline

  • 保存寄存器
  • 切页表

阶段2:内核函数

  • 执行逻辑

4.6 类比

trampoline = 安全隔离舱(airlock)


五、trapframe 的作用

5.1 内容

  • 所有通用寄存器
  • kernel_sp
  • kernel_satp
  • kernel_trap

5.2 本质

用户上下文 + 内核入口信息

5.3 作用

  • 保存现场
  • 提供返回依据

六、“一种硬件优化方案”的分析

6.1 方案描述

  • 增加寄存器保存内核页表
  • trap 自动切页表
  • 直接进入内核

6.2 优点

  • 避免 trampoline

6.3 核心问题

1. 无法确定当前进程

仍需找到:

  • kernel stack
  • trapframe

2. 无法保存寄存器

问题仍然存在:

  • 寄存器污染

3. 硬件复杂度增加

违反 RISC-V 极简原则

4. 不通用

限制 OS 设计

6.4 结论

只是转移问题,而不是解决问题


七、为什么不用 direct map

7.1 direct map 思路

在用户页表中映射内核

7.2 问题:安全漏洞

Spectre / Meltdown

  • 推测执行可读取内核数据

7.3 KPTI

  • 用户页表不包含内核
  • 内核页表独立

7.4 结果

必须使用 trampoline


八、RISC-V vs x86 设计对比

特性 x86 RISC-V
自动切栈
自动保存寄存器
trap复杂度
灵活性

8.1 本质差异

x86:硬件帮你做 RISC-V:软件自己做


九、操作系统跨架构设计

9.1 是否需要适配?

必须

9.2 分层结构

通用层

  • 调度
  • 文件系统
  • 内存管理

架构层

  • trap
  • 上下文切换
  • 页表

9.3 Linux 结构示例

arch/
  x86/
  riscv/
  arm64/

十、完整流程总结

10.1 进入内核

  1. ecall
  2. 硬件保存最小状态
  3. 跳 trampoline
  4. 保存寄存器
  5. 切页表
  6. 进入内核

10.2 返回用户

  1. 准备返回
  2. 恢复寄存器
  3. 切页表
  4. sret

10.3 核心思想

分阶段、安全、最小信任


十一、核心设计哲学总结

11.1 RISC-V

  • 极简硬件
  • 软件控制
  • 高灵活性

11.2 trampoline

必须存在的安全过渡层

11.3 sscratch

唯一可信入口锚点

11.4 OS设计原则

  • 不信任用户态
  • 分阶段执行
  • 最小权限原则

十二、总结

  • trap = 受控异常
  • trampoline = 安全桥梁
  • sscratch = 启动锚点