## 实验四（上）：线程

### 实验之前

- 阅读实验指导四。
- 从本次实验起，我们将不再提供“截至当前章节的代码框架”。你可以直接在 `master` 分支上查看代码，因为后面章节基本只会添加代码而鲜有修改。
- 实验用到的代码在 `lab-4` 分支上，与 `master` 稍有修改。

### 实验题目

1.  原理：线程切换之中，页表是何时切换的？页表的切换会不会影响程序 / 操作系统的运行？为什么？

    {% reveal %}
> 页表是在 `Process::prepare_next_thread()` 中调用 `Thread::prepare()`，其中换入了新线程的页表。
>
> 它不会影响执行，因为在中断期间是操作系统正在执行，而操作系统所用到的内核线性映射是存在于每个页表中的。
    {% endreveal %}

    <br>
2.  设计：如果不使用 `sscratch` 提供内核栈，而是像原来一样，遇到中断就直接将上下文压栈，请举出（思路即可，无需代码）：
    - 一种情况不会出现问题
    - 一种情况导致异常无法处理（指无法进入 `handle_interrupt`）
    - 一种情况导致产生嵌套异常（指第二个异常能够进行到调用 `handle_interrupt`，不考虑后续执行情况）
    - 一种情况导致一个用户进程（先不考虑是怎么来的）可以将自己变为内核进程，或以内核态执行自己的代码

    {% reveal %}
> - 只运行一个非常善意的线程，比如 `loop {}`
> - 线程把自己的 `sp` 搞丢了，比如 `mv sp, x0`。此时无法保存寄存器，也没有能够支持操作系统正常运行的栈
> - 运行两个线程。在两个线程切换的时候，会需要切换页表。但是此时操作系统运行在前一个线程的栈上，一旦切换，再访问栈就会导致缺页，因为每个线程的栈只在自己的页表中
> - 用户进程巧妙地设计 `sp`，使得它恰好落在内核的某些变量附近，于是在保存寄存器时就修改了变量的值。这相当于任意修改操作系统的控制信息
    {% endreveal %}

    <br>
3.  实验：当键盘按下 Ctrl + C 时，操作系统应该能够捕捉到中断。实现操作系统捕获该信号并结束当前运行的线程（你可能需要阅读一点在实验指导中没有提到的代码）

    <br>
4.  实验：实现进程的 `fork()`。目前的内核线程不能进行系统调用，所以我们先简化地实现为“按 F 进行 fork”。fork 后应当为目前的进程复制一份几乎一样的拷贝。

    > 旧题目：这个题目有一些问题，会导致线程中对栈上的指针失效。如果已经完成了 `clone()` 实验，推荐但不必须重新做 `fork()`。
    > 
    > 实现线程的 `clone()`。目前的内核线程不能进行系统调用，所以我们先简化地实现为“按 C 进行 clone”。clone 后应当为目前的线程复制一份几乎一样的拷贝，新线程与旧线程同属一个进程，公用页表和大部分内存空间，而新线程的栈是一份拷贝。
