From 925a08f9ae9c8d1ef5d59e55a1537242198247dc Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 26 Oct 2018 02:32:28 +0800 Subject: [PATCH] Add OSLab/exp3 report --- docs/2_OSLab/g5/exp3.md | 106 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 docs/2_OSLab/g5/exp3.md diff --git a/docs/2_OSLab/g5/exp3.md b/docs/2_OSLab/g5/exp3.md new file mode 100644 index 0000000..1b36902 --- /dev/null +++ b/docs/2_OSLab/g5/exp3.md @@ -0,0 +1,106 @@ +# 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的用户程序,方便进行性能对比 + + +