|
|
|
|
# 2018操作系统专题训练
|
|
|
|
|
|
|
|
|
|
# 实验3:实现和测试报告
|
|
|
|
|
|
|
|
|
|
计53 王润基 2015011279
|
|
|
|
|
|
|
|
|
|
2018.10.25
|
|
|
|
|
|
|
|
|
|
## 实验目标
|
|
|
|
|
|
|
|
|
|
**基于RustOS,参考sv6完成多核实现和优化。**
|
|
|
|
|
|
|
|
|
|
分为以下三个子任务:
|
|
|
|
|
|
|
|
|
|
1. 实现x86_64和RISCV下的多核启动和通信
|
|
|
|
|
2. 拓展线程管理模块,使之支持多核调度
|
|
|
|
|
3. 学习sv6进行多核优化
|
|
|
|
|
|
|
|
|
|
## 实验进展
|
|
|
|
|
|
|
|
|
|
基本完成前两项子任务,即:**实现了多核线程调度执行**。
|
|
|
|
|
|
|
|
|
|
具体的任务节点是:
|
|
|
|
|
|
|
|
|
|
1. 到10月19日,我完成了x86下的多核启动和IPI。与此同时,王纪霆完成了riscv32下的多核启动,并针对编译器原子操作缺失问题给出了临时解决方案。我合并了他的成果,至此多核的底层支持实现完毕。
|
|
|
|
|
2. 到10月25日,我完成了线程管理模块针对多处理机模型的重构,原来正常的用户程序都能在多核环境下正确执行。
|
|
|
|
|
|
|
|
|
|
## 实现方案
|
|
|
|
|
|
|
|
|
|
### x86的多核启动
|
|
|
|
|
|
|
|
|
|
实际上这部分工作已经在上学期OS课大作业中实现完毕。不过由于之后替换了x86下的bootloader,多核启动特性被暂时关闭。因此实际的任务是:**把原来Kernel中的多核启动代码,移植到新bootloader中**。
|
|
|
|
|
|
|
|
|
|
简单描述多核启动流程:
|
|
|
|
|
|
|
|
|
|
* 将其它核的启动代码放置于物理地址0x8000处
|
|
|
|
|
|
|
|
|
|
这个靠修改linker.ld,在链接阶段完成。物理地址必须4K对齐。
|
|
|
|
|
|
|
|
|
|
* 在主核初始化完毕后,调用一段操作LocalAPIC的代码(借用自xv6),向其它核发送启动IPI,让它从0x8000开始执行。主核需依次启动其它核,因为它们使用相同的栈空间,不能同时执行。
|
|
|
|
|
|
|
|
|
|
* 启动后,首先进行boot,在16位下设置段寄存器、页表、开启LongMode,直接进入64位,跳到Rust代码,再跳到Kernel入口点。
|
|
|
|
|
|
|
|
|
|
目前还未实现的特性是:
|
|
|
|
|
|
|
|
|
|
* 应该首先读取ACPI中的LocalAPIC信息,确定核的个数。
|
|
|
|
|
|
|
|
|
|
我原本计划使用RustOSDev的[ACPI库](https://github.com/rust-osdev/acpi),但它仍在开发中,功能尚不完全。
|
|
|
|
|
|
|
|
|
|
### x86的中断管理
|
|
|
|
|
|
|
|
|
|
在原来的单核环境中,系统使用PIC中断控制器和PIT时钟。
|
|
|
|
|
|
|
|
|
|
而在多核环境中,它们被IOAPIC和APIC Timer替代。
|
|
|
|
|
|
|
|
|
|
为此,我参考了xv6/sv6/Redox在这方面的实现,其中xv6实现最为简单,sv6进行了更好的对象包装,Redox提供了Rust下实现的参考。我发现这部分功能相对独立,而还没有人发布过Rust crate实现APIC功能,因此我自己实现了一个[APIC crate](https://github.com/wangrunji0408/APIC-Rust),综合参考了以上三个项目的代码。并将它用在了bootloader和kernel中。
|
|
|
|
|
|
|
|
|
|
### 合并RV工作
|
|
|
|
|
|
|
|
|
|
riscv32下的多核启动由王纪霆同学完成。我之后为它开启了IPI。
|
|
|
|
|
|
|
|
|
|
由于Rust编译器对riscv支持还不完全,不能生成原子指令,且核心库中的原子操作也没有为rv适配,具体体现为:rv下只有32位数据的原子操作,因此没有AtomicBool;此外即使用AtomicUsize实现的Mutex也不能正常工作,陈秋昊同学经过实验推测是Rust编译器内部实现的问题。
|
|
|
|
|
|
|
|
|
|
针对上述问题,王纪霆的解决方案是修改Kernel中Mutex的实现。而我在考虑直接修改核心库的实现,这样上层不用做修改,spin库也能正常使用。不过这一想法还没付诸实施。
|
|
|
|
|
|
|
|
|
|
### 多核线程管理
|
|
|
|
|
|
|
|
|
|
以上几个任务完成了平台相关的工作,接下来的任务都是平台无关的。
|
|
|
|
|
|
|
|
|
|
这部分主要工作是重构了process模块,使之支持多处理机模型。
|
|
|
|
|
|
|
|
|
|
在原来的设计中,用一个Processor类维护所有线程的状态,同时负责线程切换,对外提供exit/wait/sleep等控制接口。它是全局唯一的,用一个大锁包起来。
|
|
|
|
|
|
|
|
|
|
在新的设计中,线程状态和实际执行被分开。其中线程状态由[ProcessManager](http://os.cs.tsinghua.edu.cn/oscourse/ProcessManager)类管理,另外的Processor类负责线程的实际执行。前者是全局唯一的,后者是CPU核的抽象,每个核对应一个。由于它们都需要定义为全局变量,因此对外接口都是&self,内部用[SpinLock](http://os.cs.tsinghua.edu.cn/oscourse/SpinLock)或[UnsafeCell](http://os.cs.tsinghua.edu.cn/oscourse/UnsafeCell)维护可变状态。
|
|
|
|
|
|
|
|
|
|
参考xv6/ucore的设计,每个CPU核在初始化完毕后都进入自己的“调度线程”,无限重复以下操作:
|
|
|
|
|
|
|
|
|
|
- 从[ProcessManager](http://os.cs.tsinghua.edu.cn/oscourse/ProcessManager)中取出一个可执行线程的上下文(委托内部的Scheduler决定选哪个)
|
|
|
|
|
- 执行上下文切换运行该线程
|
|
|
|
|
- 线程运行时(主动地,或在时钟中断时被动地)调用yield切换回调度线程
|
|
|
|
|
- 将运行完的线程上下文交还[ProcessManager](http://os.cs.tsinghua.edu.cn/oscourse/ProcessManager)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
上述内容实现后,就得到了一个最基础的线程执行框架:
|
|
|
|
|
|
|
|
|
|
* 每个进程有3个状态:Ready等待执行,Running正在执行,Sleep不可执行
|
|
|
|
|
* 若干处理机并发地从一个公共线程池里取出、执行、放回
|
|
|
|
|
|
|
|
|
|
注意这个实现不包括:
|
|
|
|
|
|
|
|
|
|
* 维护线程间的父子关系(所有线程是平级的)
|
|
|
|
|
* 维护线程间的等待关系(等待和唤醒应该由等待队列完成)
|
|
|
|
|
|
|
|
|
|
这部分工作的前期大量时间投入在构思设计上:如何界定类的指责和它们的交互关系?更具体的——哪里用锁?哪里可变?每个需求如何实现?受制于Rust类型系统和安全机制的约束,要设计一个编译通过的方案并不容易。
|
|
|
|
|
|
|
|
|
|
前期设计断断续续用了几天时间,而编写代码只用了一天,而其中相当一部分时间在与编译器作斗争。不过在编译通过后,仅修复了几个明显的Bug就能正常运行了!完全没有数据竞争问题!不得不承认Rust语言在处理并发问题上有很大优势。
|
|
|
|
|
|
|
|
|
|
## 未来计划
|
|
|
|
|
|
|
|
|
|
1. 在HiFive开发版上运行RustOS
|
|
|
|
|
2. 更深入研究sv6,移植其中的多核优化特性
|
|
|
|
|
3. 完善系统调用,争取能跑起来sv6的用户程序,方便进行性能对比
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|