## 线程的创建

接下来，我们的第一个目标就是创建一个线程并且让他运行起来。一个线程要开始运行，需要这些准备工作：

- 建立页表映射，需要包括以下映射空间：
  - 线程所执行的一段指令
  - 线程执行栈
  - *操作系统的部分内存空间*
- 设置起始执行的地址
- 初始化各种寄存器，比如 `sp`
- 可选：设置一些执行参数（例如 `argc` 和 `argv`等 ）

思考：为什么线程即便与操作系统无关，也需要在内存中映射操作系统的内存空间呢？

{% reveal %}
> 当发生中断时，需要跳转到 `stvec` 所指向的中断处理过程。如果操作系统的内存不在页表之中，将无法处理中断。
>
> 当然，也不是所有操作系统的代码都需要被映射，但是为了实现简便，我们会为每个进程的页表映射全部操作系统的内存。而由于这些页表都标记为**内核权限**（即 `U` 位为 0），也不必担心用户线程可以随意访问。
{% endreveal %}

### 执行第一个线程

因为启动线程需要修改各种寄存器的值，所以我们又要使用汇编了。不过，这一次我们只需要对 `interrupt.asm` 稍作修改就可以了。

在 `interrupt.asm` 中的 `__restore` 标签现在就能派上用途了。原本这段汇编代码的作用是将之前所保存的 `Context` 恢复到寄存器中，而现在我们让它使用一个精心设计的 `Context`，就可以让程序在恢复后直接进入我们的新线程。

首先我们稍作修改，添加一行 `mv sp, a0`。原本这里是读取之前存好的 `Context`，现在我们让其从 `a0` 中读取我们设计好的 `Context`。这样，我们可以直接在 Rust 代码中调用 `__restore(context)`。

{% label %}os/src/interrupt/interrupt.asm{% endlabel %}
```asm
__restore:
    mv      sp, a0  # 加入这一行
    # ...
```

#### 那么我们需要如何设计 `Context` 呢？

- 通用寄存器
  - `sp`：应当指向该线程的栈顶
  - `a0`-`a7`：按照函数调用规则，用来传递参数
  - `ra`：线程执行完应该跳转到哪里呢？在后续**系统调用**章节我们会介绍正确的处理方式。现在，我们先将其设为一个不可执行的地址，这样线程一结束就会触发页面异常
- `sepc`
  - 执行 `sret` 指令后会跳转到这里，所以 `sepc` 应当存储线程的入口地址（执行的函数地址）
- `sstatus`
  - `spp` 位按照用户态或内核态有所不同
  - `spie` 位为 1

> **[info] `sstatus` 标志位的具体意义**
>
> - `spp`：中断前系统处于内核态（1）还是用户态（0）
> - `sie`：内核态是否允许中断。对用户态而言，无论 `sie` 取何值都开启中断
> - `spie`：中断前是否开中断（用户态中断时可能 `sie` 为 0）
>
> **硬件处理流程**
>
> - 在中断发生时，系统要切换到内核态。此时，**切换前的状态**会被保存在 **`spp`** 位中（1 表示切换前处于内核态）。同时，**切换前是否开中断**会被保存在 **`spie`** 位中，而 `sie` 位会被置 0，表示关闭中断。
> - 在中断结束，执行 `sret` 指令时，会根据 `spp` 位的值决定 `sret` 执行后是处于内核态还是用户态。与此同时，`spie` 位的值会被写入 `sie` 位，而 `spie` 位置 1。这样，特权状态和中断状态就全部恢复了。
>
> **为何如此繁琐？**
>
> - 特权状态：  
>   中断处理流程必须切换到内核态，所以中断时需要用 `spp` 来保存之前的状态。  
>   回忆计算机组成原理的知识，`sret` 指令必须同时完成跳转并切换状态的工作。
> - 中断状态：  
>   中断刚发生时，必须关闭中断，以保证现场保存的过程不会被干扰。同理，现场恢复的过程也必须关中断。因此，需要有以上两个硬件自动执行的操作。  
>   由于中断可能嵌套，在保存现场后，根据中断的种类，可能会再开启部分中断的使能。

设计好 `Context` 之后，我们只需要将它应用到所有的寄存器上（即执行 `__restore`），就可以切换到第一个线程了。

{% label %}os/src/main.rs: rust_main(){% endlabel %}
```rust
extern "C" {
    fn __restore(context: usize);
}
// 获取第一个线程的 Context，具体原理后面讲解
let context = PROCESSOR.lock().prepare_next_thread();
// 启动第一个线程
unsafe { __restore(context as usize) };
unreachable!()
```

#### 为什么 `unreachable`

我们直接调用的 `__restore` 并没有 `ret` 指令，甚至 `ra` 都会被 `Context` 中的数值直接覆盖。这意味着，一旦我们执行了 `__restore(context)`，程序就无法返回到调用它的位置了。**注：直接 jump 是一个非常危险的操作**。

但是没有关系，我们也不需要这个函数返回。因为开始执行第一个线程，意味着操作系统的初始化已经完成，再回到 `rust_main()` 也没有意义了。甚至原本我们使用的栈 `bootstack`，也可以被回收（不过我们现在就丢掉不管吧）。

#### 在启动时不打开中断

现在，我们会在线程开始运行时开启中断，而在操作系统初始化的过程中是不应该有中断的。所以，我们删去之前设置「开启中断」的代码。

{% label %}os/interrupt/timer.rs{% endlabel %}
```rust
/// 初始化时钟中断
///
/// 开启时钟中断使能，并且预约第一次时钟中断
pub fn init() {
    unsafe {
        // 开启 STIE，允许时钟中断
        sie::set_stimer();
        // （删除）开启 SIE（不是 sie 寄存器），允许内核态被中断打断
        // sstatus::set_sie();
    }
    // 设置下一次时钟中断
    set_next_timeout();
}
```

### 小结

为了执行一个线程，我们需要初始化所有寄存器的值。为此，我们选择构建一个 `Context` 然后跳转至 `interrupt.asm` 中的 `__restore` 来执行，用这个 `Context` 来写入所有寄存器。

#### 思考

`__restore` 现在会将 `a0` 寄存器视为一个 `*mut Context` 来读取，因此我们在执行第一个线程时只需调用 `__restore(context)`。

那么，如果是程序发生了中断，执行到 `__restore` 的时候，`a0` 的值又是谁赋予的呢？
