You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

107 lines
5.9 KiB

# 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的用户程序方便进行性能对比