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



