任务管理分析
在上一节中,我们介绍了任务生命周期和 TCB 的概念。本节将深入探讨任务管理的具体实现细节,包括任务上下文的保存与恢复、任务切换机制以及全局任务管理器的设计。
任务上下文 TaskContext
Section titled “任务上下文 TaskContext”在多任务系统中,操作系统需要频繁地在不同任务之间进行切换。为了保证任务在被切换回来时能够继续正确执行,我们需要保存任务被切换时的执行状态,即任务上下文 (Task Context)。
在 kernel/src/task/context.rs 中,我们定义了 TaskContext 结构体:
寄存器保存策略
Section titled “寄存器保存策略”你可能会疑问,为什么 TaskContext 只保存了部分寄存器?这与 RISC-V 架构的函数调用约定(Calling Convention/ABI)有关。
| 类别 | 寄存器 | 负责方 | 是否需要保存到 TaskContext |
|---|---|---|---|
| Caller-saved | t0–t6, a0–a7, ra (部分情况) | 调用者保存 | 否。__switch 被视为一个普通函数调用,调用者(编译器生成的代码)在调用前已经保存了这些寄存器(通常保存在栈上)。 |
| Callee-saved | s0–s11, sp, ra (部分情况) | 被调用者保存 | 是。作为被调用者(__switch),我们需要手动保存这些寄存器,以保证函数返回时状态一致。 |
简而言之,__switch 的执行过程在编译器看来就是一个普通的函数调用。因此,我们只需要配合编译器,手动保存那些编译器认为”被调用者有责任保存”的寄存器即可。
任务上下文初始化
Section titled “任务上下文初始化”TaskContext 提供了初始化的方法:
goto_restore 方法巧妙地构造了一个”伪造”的调用历史。当调度器通过 __switch 切换到这个新任务并执行 ret 指令时,CPU 会跳转到 ra 寄存器中保存的地址,即 __restore_sp。随后,__restore_sp 会从内核栈恢复 TrapContext,并使用 sret 指令进入用户态,从而启动任务。
寄存器换栈 __switch
Section titled “寄存器换栈 __switch”任务切换的核心逻辑由汇编函数 __switch 实现,它定义在 kernel/src/task/switch.S 中:
使用汇编实现 __switch 是必须的,因为我们需要直接控制 CPU 寄存器和栈指针,并且不能受到编译器指令重排或自动寄存器分配的干扰。高级语言无法提供这种级别的控制。
__switch 的执行过程可以概括为:
- 保存现场:将当前的
ra,sp,s0-s11寄存器保存到current_task_cx_ptr指向的内存位置(当前任务的 TCB 中)。 - 恢复现场:从
next_task_cx_ptr指向的内存位置加载ra,sp,s0-s11寄存器(恢复下一个任务的状态)。 - 跳转执行:执行
ret指令。此时ra寄存器已经变为下一个任务的返回地址,控制流随之转移到下一个任务。
调度器 TaskManager
Section titled “调度器 TaskManager”TASK_MANAGER通过 lazy_static! 宏在内核中全局存在。
你可能会问,为什么需要 UPSafeCell?这就是内部可变性所要做的事情。
Rust 的过程中,不可变引用(&T)和可变引用(&mut T)的排他性规则已经让你记忆犹新。
TaskManagerInner是为了修改其内部的状态(如切换当前任务、更改 TCB 状态),但全局单例拿到的往往是不可变引用 &TaskManager。
在单核操作系统(Uniprocessor,UP)环境下,我们不担心多核并发导致的问题,只需在运行时借助内部可变性屏蔽掉 Rust 在编译期的借用检查。
任务调度、分发与上下文切换
Section titled “任务调度、分发与上下文切换”任务调度算法 find_next_task
Section titled “任务调度算法 find_next_task”该函数负责执行调度策略,决定下一个将要获得 CPU 使用权的任务。
在本章中,我们采用简单的 时间片轮转 (Round-Robin, RR) 调度算法。为了保证公平性,算法逻辑如下:
从当前任务 (current_task) 的下一个位置开始,循环遍历任务列表。一旦找到第一个状态为 TaskStatus::Ready 的任务,即将其作为下一个运行的任务返回。如果遍历一圈后仍未找到就绪任务,说明所有用户程序均已完成或退出。
任务分发与上下文切换 run_next_task
Section titled “任务分发与上下文切换 run_next_task”该函数负责执行实际的任务切换流程,包括状态更新和硬件上下文的切换。
当 find_next_task 成功返回下一个任务的 ID 后,run_next_task 将执行以下关键操作:
- 更新状态:将选中的下一个任务状态更新为
Running,并将调度器的current_task更新为该任务 ID。 - 切换上下文:构造当前任务和下一个任务的上下文指针,并调用汇编函数
__switch。