# 线程管理模块

【部分内容已经过时】模块经过了重构，支持多核调度

## 概述

线程管理模块的职责是完成CPU资源的管理。它建立在硬件提供的中断及上下文切换机制之上，提供线程的管理和调度功能。此模块功能相当于ucore lab4。

注意它不负责管理线程使用的其他资源（如内存、文件），也不了解用户态和内核态的切换（由使用者构造合适的上下文来解决）。目前的设计基于单核CPU，未来扩展到多核时可能需要重新调整。

具体而言，使用者只需实现底层的上下文切换过程，即可享用内核线程的功能。此模块提供了直接操作线程的接口，如fork、exit、wait，用于实现系统调用。在此基础上，还封装了和[`std::thread`](https://doc.rust-lang.org/std/thread/index.html)完全相同的上层接口，使得用户态的Rust多线程程序可以方便地迁移到内核中。

## 实现

此模块作为独立的crate存在，代码位于：`crate/process`

src目录结构及各文件功能如下：

```
.
├── lib.rs          根模块
├── processor.rs    一个CPU核的管理器
├── scheduler.rs    调度算法
├── thread.rs       实现std::thread接口
├── event_hub.rs    简单事件管理器
└── util.rs         其它工具
```

理论上可以在Linux用户态实现一个模拟的上下文切换，进而编写单元测试，但这个想法尚未实现。

### 上下文接口

位于`processor.rs`，只有一个很简单的trait：

{% code title="processor.rs" %}

```rust
pub trait Context: Debug {
    /// 将当前CPU切换到另一个上下文
    unsafe fn switch(&mut self, target: &mut Self);
    
    /// 构造一个新的内核态上下文
    /// 只用于实现thread::spawn
    fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Self;
}
```

{% endcode %}

这个上下文实际上是保存在内核栈上的寄存器和中断帧TrapFrame，切换switch过程实际就是一段保存和恢复寄存器的汇编代码。

读者可分别查看它在x86\_64和RISCV32下的实现，了解具体使用方法：

* arch/x86\_64/interrupt/trapframe.rs
* arch/riscv32/context.rs

为了创建一个新的线程，使用者需要分配好一个内核栈，并精心构造栈顶的初始数据，使得switch后能正确设置CPU状态。Context的构造函数需由使用者自己定义，不会在接口中体现。RustOS中实现了三种构造函数，分别用于创建内核线程、用户线程、fork已有线程。

这套接口是面向软件上下文切换设计的，如果CPU支持硬件切换上下文，可能实现会更简单。

关于上下文切换的细节和原理，可参考 [第一个进程 | xv6 中文文档](https://th0ar.gitbooks.io/xv6-chinese/content/content/chapter1.html)。

### 调度器

位于`scheduler.rs`，首先定义了接口：

{% code title="scheduler.rs" %}

```rust
/// 调度器接口
/// 本质是一个就绪态线程的优先队列
pub trait Scheduler {
    /// 添加一个就绪线程
    fn insert(&mut self, pid: Pid);
    /// 移除一个就绪线程
    fn remove(&mut self, pid: Pid);
    /// 选出下一个要执行的线程，但不移除它
    fn select(&mut self) -> Option<Pid>;
    /// 每隔固定时间调用此函数，更新当前线程的时间片信息
    /// 返回是否该线程时间片用完，需要调度
    fn tick(&mut self, current: Pid) -> bool;   // need reschedule?
    /// 设置线程优先级
    fn set_priority(&mut self, pid: Pid, priority: u8);
}
```

{% endcode %}

之后提供了两个调度算法的实现：时间片轮转（RR），Stride。

### Processor

位于`processor.rs`，其中包含以下结构：

* Status：枚举类型，保存线程状态
* Event：枚举类型，描述未来的调度事件
* WaitResult：枚举类型，作为返回值描述线程的等待状态
* Process：线程控制块
* Processor：完成实际CPU管理的主体

Processor的完整定义如下：

```rust
Processor_<T: Context, S: Scheduler>
```

使用时需指定T和S的实现类型，然后定义一个全局实例：

{% code title="process/mod.rs" %}

```rust
type Processor = Processor_<Context, StrideScheduler>;

// Once用来支持稍后手动初始化
// 这里的Mutex实际是一个修改后的SpinNoIrqLock
// 即在lock期间同时关闭中断的自旋锁
// 目的是防止中断时再中断，导致死锁
static PROCESSOR: Once<Mutex<Processor>> = Once::new();

// 初始化示意
fn process_init() {
    PROCESSOR.call_once(|| Mutex::new(
        // 构造一个实例
        Processor::new(
            // 当前第一个线程的Context，内容为空
            unsafe { Context::new_init() },
            StrideScheduler::new(5),
        )
    ));
}
```

{% endcode %}

创建新线程时，首先自己构造Context，然后add：

```rust
// 定义idle线程
extern fn idle(arg: usize) -> ! {
    loop {}
}
// 构造Context并添加到Processor
processor.add(Context::new_kernel(entry: idle, arg: 0));
```

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即可。对使用者而言，需要进行以下操作：

```rust
// 首先我们已经有了一个Processor的全局实例
type Processor = Processor_<Context, StrideScheduler>;
pub static PROCESSOR: Once<Mutex<Processor>> = Once::new();

// 实现ThreadSupport接口
pub struct ThreadSupportImpl;
impl ThreadSupport for ThreadSupportImpl {
    type Context = Context;
    type Scheduler = StrideScheduler;
    type ProcessorGuard = MutexGuard<'static, Processor>;
    // 上面都是铺垫，下面才是重点
    fn processor() -> Self::ProcessorGuard {
        PROCESSOR.try().unwrap().lock()
    }
}

// ‘实例化’ThreadMod，并假装它是一个mod
#[allow(non_camel_case_types)]
pub type thread = ThreadMod<ThreadSupportImpl>;

// 然后就可以把它当作`std::thread`来用了
use thread;
let t = thread::current();

// 然而假的毕竟是假的……
let t: thread::Thread;   // Compile ERROR!
```

之所以用这样一种奇怪的实现方式，是为了这个需求：我希望实现一些全局静态函数，同时希望它们的底层依赖是可替换的。std::thread本身的实现方式是用条件编译，对不同系统使用不同的底层函数，然而我们这个没法这么搞。另一种可能的实现方式是用一个大宏把整个包起来，使用时再展开，不过这样IDE就无法实时分析这部分代码了……

至于thread本身的实现，除了spawn之外都比较简单，读者可查看源码了解实现细节。

## TODO

* [x] 多核支持


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://rcore.gitbook.io/rust-os-docs/jin-cheng-guan-li-mo-kuai.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
