线程管理模块
【部分内容已经过时】模块经过了重构,支持多核调度
概述
线程管理模块的职责是完成CPU资源的管理。它建立在硬件提供的中断及上下文切换机制之上,提供线程的管理和调度功能。此模块功能相当于ucore lab4。
注意它不负责管理线程使用的其他资源(如内存、文件),也不了解用户态和内核态的切换(由使用者构造合适的上下文来解决)。目前的设计基于单核CPU,未来扩展到多核时可能需要重新调整。
具体而言,使用者只需实现底层的上下文切换过程,即可享用内核线程的功能。此模块提供了直接操作线程的接口,如fork、exit、wait,用于实现系统调用。在此基础上,还封装了和std::thread
完全相同的上层接口,使得用户态的Rust多线程程序可以方便地迁移到内核中。
实现
此模块作为独立的crate存在,代码位于:crate/process
src目录结构及各文件功能如下:
理论上可以在Linux用户态实现一个模拟的上下文切换,进而编写单元测试,但这个想法尚未实现。
上下文接口
位于processor.rs
,只有一个很简单的trait:
这个上下文实际上是保存在内核栈上的寄存器和中断帧TrapFrame,切换switch过程实际就是一段保存和恢复寄存器的汇编代码。
读者可分别查看它在x86_64和RISCV32下的实现,了解具体使用方法:
arch/x86_64/interrupt/trapframe.rs
arch/riscv32/context.rs
为了创建一个新的线程,使用者需要分配好一个内核栈,并精心构造栈顶的初始数据,使得switch后能正确设置CPU状态。Context的构造函数需由使用者自己定义,不会在接口中体现。RustOS中实现了三种构造函数,分别用于创建内核线程、用户线程、fork已有线程。
这套接口是面向软件上下文切换设计的,如果CPU支持硬件切换上下文,可能实现会更简单。
关于上下文切换的细节和原理,可参考 第一个进程 | xv6 中文文档。
调度器
位于scheduler.rs
,首先定义了接口:
之后提供了两个调度算法的实现:时间片轮转(RR),Stride。
Processor
位于processor.rs
,其中包含以下结构:
Status:枚举类型,保存线程状态
Event:枚举类型,描述未来的调度事件
WaitResult:枚举类型,作为返回值描述线程的等待状态
Process:线程控制块
Processor:完成实际CPU管理的主体
Processor的完整定义如下:
使用时需指定T和S的实现类型,然后定义一个全局实例:
创建新线程时,首先自己构造Context,然后add:
Processor目前的设计是让【修改线程状态】和【执行线程切换】两种操作尽量分离。
具体而言,【执行线程切换】只能通过schedule函数进行,它会判断当前线程状态是否为Running,若不是,委托调度器选出下一个目标线程,执行切换,等到下次切换回来时退出schedule函数。
而其它大部分函数(除current_wait_for),只会【修改线程状态】,而不会执行线程切换。其中exit/kill/sleep/wakeup/yield_now函数,会调用set_status进行显式的状态修改;而tick函数,会根据当前时刻的调度事件,进行隐式的状态修改。
在原版ucore中,每个CPU核有一个专门的调度线程,进行线程调度时,首先切换到调度线程,然后再切换到下一个线程。而在RustOS中是没有调度线程的,源线程直接调用schedule函数切换到下一个线程,能减少一次上下文切换。这两种实现方式哪种更好,欢迎大家一起讨论。
std::thread接口
位于thread.rs
,首先定义了ThreadSupport
接口提供底层支持,然后定义了一个空结构ThreadMod<S: ThreadSupport>
实现和std::thread相同的上层接口。
由于这部分仅仅是在Processor外面又套了一层,因此所谓的ThreadSupport
接口只需能访问到全局Processor即可。对使用者而言,需要进行以下操作:
之所以用这样一种奇怪的实现方式,是为了这个需求:我希望实现一些全局静态函数,同时希望它们的底层依赖是可替换的。std::thread本身的实现方式是用条件编译,对不同系统使用不同的底层函数,然而我们这个没法这么搞。另一种可能的实现方式是用一个大宏把整个包起来,使用时再展开,不过这样IDE就无法实时分析这部分代码了……
至于thread本身的实现,除了spawn之外都比较简单,读者可查看源码了解实现细节。
TODO
Last updated