执行上下文与切换机制
本文档详细阐述了 comix 内核中用于实现多任务并发执行的两种核心上下文(TrapFrame 和 Context)及其切换机制。
1. 上下文的两种类型
在 comix 中,"上下文" 指的是恢复一个任务执行所必需的CPU状态。根据触发场景的不同,我们有两种不同的上下文结构:
TrapFrame(中断上下文): 当发生中断、异常或系统调用时,由硬件和底层汇编代码trap_entry保存的 完整 CPU状态。它是一个任务在被意外打断时的精确快照。Context(任务上下文): 当任务主动放弃CPU (yield) 或时间片用尽时,由调度器和底层汇编代码__switch保存的 最小 CPU状态。它仅包含恢复任务执行流所必需的寄存器。
这两种上下文的设计,是为了在不同场景下实现效率和功能的平衡。
2. TrapFrame 与中断上下文切换
TrapFrame 是处理所有硬件中断、异常和系统调用的基础。
源码链接:
TrapFrame结构体:os/src/arch/riscv/trap/mod.rs- 汇编入口/出口:
os/src/arch/riscv/trap/trap_entry.S
2.1. TrapFrame 的构成
TrapFrame 结构体保存了任务被中断时的 几乎所有CPU状态。
| 字段 | 核心内容 | 作用 |
|---|---|---|
x[32] | 32个通用寄存器 (x0-x31) | 保存了任务在中断前的所有计算状态和参数。 |
sstatus | sstatus 寄存器,包含特权级、中断使能等状态。 | 用于在 sret 时恢复正确的特权级和中断状态。 |
sepc | sepc 寄存器,保存了被中断指令的地址。 | sret 指令会跳转到此地址,从中断处继续执行。 |
kernel_sp | 内核栈指针。 | 在 trap_entry 中用于切换到正确的内核栈。 |
2.2. TrapFrame 的工作原理
当中断发生时,CPU硬件和软件会协同完成一次精确的上下文保存与恢复流程:
| 步骤 | 操作内容 | 核心代码/指令 |
|---|---|---|
| 1. 硬件操作 | 1. CPU暂停当前指令,根据 scause 记录中断原因。2. 将当前PC存入 sepc。3. 切换到S模式。 4. 跳转到 stvec 寄存器指向的地址。 | (硬件自动完成) |
| 2. 保存上下文 | 1. trap_entry 在当前任务的内核栈上分配 TrapFrame 空间。2. 将 所有 通用寄存器和 sstatus、sepc 保存到 TrapFrame 中。 | trap_entry in trap_entry.S |
| 3. 中断处理 | trap_entry 调用 trap_handler (Rust函数),并传入 TrapFrame 的可变引用。trap_handler 可以读取和修改 TrapFrame。 | trap_handler in trap_handler.rs |
| 4. 恢复上下文 | 1. trap_handler 返回后,__restore 从 TrapFrame 中将所有寄存器值加载回CPU。2. sret 指令原子地恢复 pc、特权级和中断状态。 | __restore 和 sret in trap_entry.S |
应用场景:
- 系统调用:
trap_handler从TrapFrame的a7读取系统调用号,从a0-a6读取参数,并将返回值写入a0。 - 任务抢占: 时钟中断处理函数可以通过修改
sepc来强制任务在恢复时跳转到调度器代码,从而实现抢占。
3. Context 与任务调度上下文切换
Context 用于常规的、非中断驱动的任务切换,追求的是极致的效率。
源码链接:
Context结构体:os/src/arch/riscv/kernel/context.rs- 切换汇编代码:
os/src/arch/riscv/kernel/switch.S
3.1. Context 的构成
Context 仅保存了 RISC-V 调用约定中 被调用者保存 (callee-saved) 的寄存器。
| 字段 | 核心内容 | 作用 |
|---|---|---|
ra | 返回地址寄存器 (x1) | 保存了调用 __switch 后的返回点,是恢复执行流的关键。 |
sp | 栈指针寄存器 (x2) | 指向任务的内核栈顶。 |
s[12] | s0-s11 ( x8-x9, x18-x27) | 调用约定要求函数在返回前必须恢复这些寄存器,因此任务切换时必须保存。 |
为什么只保存这些?
因为调用者保存(caller-saved)的寄存器(如 a0-a7, t0-t6)由调用 __switch 的函数(即 schedule)负责维护。编译器已经保证了在 __switch 调用前后,这些寄存器的值对于 schedule 函数是无损的。这使得 Context 非常小,切换速度极快。
3.2. Context 的工作原理
当 schedule() 函数决定切换任务时(例如,当前任务调用 yield_task()),会执行以下流程:
| 步骤 | 操作内容 | 核心代码/指令 |
|---|---|---|
| 1. 准备切换 | schedule() 调用调度器的 prepare_switch(),获取旧任务和新任务的 Context 指针。 | schedule() in scheduler/mod.rs |
| 2. 保存与恢复 | schedule() 调用 __switch(old_ctx, new_ctx)。1. __switch 将 ra, sp, s0-s11 保存到 old_ctx。2. __switch 从 new_ctx 恢复 ra, sp, s0-s11。 | __switch in switch.S |
| 3. 切换执行流 | __switch 的 ret 指令会跳转到刚刚从 new_ctx 恢复的 ra 地址,从而将CPU控制权无缝转移给新任务。 | ret in switch.S |
这个流程就像两个函数调用彼此的中间点,实现了两个独立执行流(任务)之间的切换。
总结
TrapFrame 和 Context 共同构成了 comix 内核的上下文切换基石:
TrapFrame是 重量级 的全状态快照,用于处理与硬件交互的、不可预期的中断事件。Context是 轻量级 的执行流锚点,用于处理可预期的、由调度器驱动的任务切换。
通过这两种机制的协同工作,内核既能高效地响应硬件事件,又能快速地在任务之间进行调度。
执行上下文与切换机制
本文档详细阐述了 comix 内核中用于实现多任务并发执行的两种核心上下文(TrapFrame 和 Context)及其切换机制。
1. 上下文的两种类型
在 comix 中,"上下文" 指的是恢复一个任务执行所必需的CPU状态。根据触发场景的不同,我们有两种不同的上下文结构:
TrapFrame(中断上下文): 当发生中断、异常或系统调用时,由硬件和底层汇编代码trap_entry保存的 完整 CPU状态。它是一个任务在被意外打断时的精确快照。Context(任务上下文): 当任务主动放弃CPU (yield) 或时间片用尽时,由调度器和底层汇编代码__switch保存的 最小 CPU状态。它仅包含恢复任务执行流所必需的寄存器。
这两种上下文的设计,是为了在不同场景下实现效率和功能的平衡。
2. TrapFrame 与中断上下文切换
TrapFrame 是处理所有硬件中断、异常和系统调用的基础。
源码链接:
TrapFrame结构体:os/src/arch/riscv/trap/mod.rs- 汇编入口/出口:
os/src/arch/riscv/trap/trap_entry.S
2.1. TrapFrame 的构成
TrapFrame 结构体保存了任务被中断时的 几乎所有CPU状态。
| 字段 | 核心内容 | 作用 |
|---|---|---|
x[32] | 32个通用寄存器 (x0-x31) | 保存了任务在中断前的所有计算状态和参数。 |
sstatus | sstatus 寄存器,包含特权级、中断使能等状态。 | 用于在 sret 时恢复正确的特权级和中断状态。 |
sepc | sepc 寄存器,保存了被中断指令的地址。 | sret 指令会跳转到此地址,从中断处继续执行。 |
kernel_sp | 内核栈指针。 | 在 trap_entry 中用于切换到正确的内核栈。 |
2.2. TrapFrame 的工作原理
当中断发生时,CPU硬件和软件会协同完成一次精确的上下文保存与恢复流程:
| 步骤 | 操作内容 | 核心代码/指令 |
|---|---|---|
| 1. 硬件操作 | 1. CPU暂停当前指令,根据 scause 记录中断原因。2. 将当前PC存入 sepc。3. 切换到S模式。 4. 跳转到 stvec 寄存器指向的地址。 | (硬件自动完成) |
| 2. 保存上下文 | 1. trap_entry 在当前任务的内核栈上分配 TrapFrame 空间。2. 将 所有 通用寄存器和 sstatus、sepc 保存到 TrapFrame 中。 | trap_entry in trap_entry.S |
| 3. 中断处理 | trap_entry 调用 trap_handler (Rust函数),并传入 TrapFrame 的可变引用。trap_handler 可以读取和修改 TrapFrame。 | trap_handler in trap_handler.rs |
| 4. 恢复上下文 | 1. trap_handler 返回后,__restore 从 TrapFrame 中将所有寄存器值加载回CPU。2. sret 指令原子地恢复 pc、特权级和中断状态。 | __restore 和 sret in trap_entry.S |
应用场景:
- 系统调用:
trap_handler从TrapFrame的a7读取系统调用号,从a0-a6读取参数,并将返回值写入a0。 - 任务抢占: 时钟中断处理函数可以通过修改
sepc来强制任务在恢复时跳转到调度器代码,从而实现抢占。
3. Context 与任务调度上下文切换
Context 用于常规的、非中断驱动的任务切换,追求的是极致的效率。
源码链接:
Context结构体:os/src/arch/riscv/kernel/context.rs- 切换汇编代码:
os/src/arch/riscv/kernel/switch.S
3.1. Context 的构成
Context 仅保存了 RISC-V 调用约定中 被调用者保存 (callee-saved) 的寄存器。
| 字段 | 核心内容 | 作用 |
|---|---|---|
ra | 返回地址寄存器 (x1) | 保存了调用 __switch 后的返回点,是恢复执行流的关键。 |
sp | 栈指针寄存器 (x2) | 指向任务的内核栈顶。 |
s[12] | s0-s11 ( x8-x9, x18-x27) | 调用约定要求函数在返回前必须恢复这些寄存器,因此任务切换时必须保存。 |
为什么只保存这些?
因为调用者保存(caller-saved)的寄存器(如 a0-a7, t0-t6)由调用 __switch 的函数(即 schedule)负责维护。编译器已经保证了在 __switch 调用前后,这些寄存器的值对于 schedule 函数是无损的。这使得 Context 非常小,切换速度极快。
3.2. Context 的工作原理
当 schedule() 函数决定切换任务时(例如,当前任务调用 yield_task()),会执行以下流程:
| 步骤 | 操作内容 | 核心代码/指令 |
|---|---|---|
| 1. 准备切换 | schedule() 调用调度器的 prepare_switch(),获取旧任务和新任务的 Context 指针。 | schedule() in scheduler/mod.rs |
| 2. 保存与恢复 | schedule() 调用 __switch(old_ctx, new_ctx)。1. __switch 将 ra, sp, s0-s11 保存到 old_ctx。2. __switch 从 new_ctx 恢复 ra, sp, s0-s11。 | __switch in switch.S |
| 3. 切换执行流 | __switch 的 ret 指令会跳转到刚刚从 new_ctx 恢复的 ra 地址,从而将CPU控制权无缝转移给新任务。 | ret in switch.S |
这个流程就像两个函数调用彼此的中间点,实现了两个独立执行流(任务)之间的切换。
总结
TrapFrame 和 Context 共同构成了 comix 内核的上下文切换基石:
TrapFrame是 重量级 的全状态快照,用于处理与硬件交互的、不可预期的中断事件。Context是 轻量级 的执行流锚点,用于处理可预期的、由调度器驱动的任务切换。
通过这两种机制的协同工作,内核既能高效地响应硬件事件,又能快速地在任务之间进行调度。