> For the complete documentation index, see [llms.txt](https://nankai.gitbook.io/ucore-os-on-risc-v64/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://nankai.gitbook.io/ucore-os-on-risc-v64/lab6/zai-ci-ren-shi-jin-cheng-qie-huan.md).

# 再次认识进程切换

我们在第四章已经简单了解过了在内核启动过程中进程切换的过程，在这一节我们再来重新回顾一下这些内容，并且深入讨论下其中的几个细节。

首先我们需要考虑的是，什么时候可以进行进程的切换。这里可以主要分为下面几种情况：

1. 进程主动放弃当前的CPU资源，比如显式调用`wait`或`sleep`通知操作系统当前进程需要等待
2. 进程想要获取的资源当前不可用，比如尝试获得未被释放的锁，或进行磁盘操作的时候
3. 进程由于外部中断被打断进入内核态，内核发现某些条件满足（比如当前进程时间片用尽），进行进程切换

在我们实现的ucore中，内核进程是**不可抢占**的。这也就意味着当内核执行的时候，另一个内核进程不可以夺走它的的CPU资源。但这是不是就意味着一个内核进程执行的时候，它就会一直执行到结束呢？虽说内核进程不可以抢占，但是它可以主动放弃自己占有的CPU资源。如果不这样设计的话，内核当中很有可能出现各种死锁导致内核崩溃。

另一方面，内核不能相信用户进程不会无限执行下去，所以需要提供手段在用户进程执行的时候打断他，比如时钟中断。在ucore的实现中，用户进程可以随时被打断进入内核，操作系统会检查当前进程是否需要调度，从而把运行的机会交给别的进程。

由于内核进程是不可抢占的，所以我们在内核中有许多地方使用了显式的函数调用来进行调度，主要有以下几个地方：

| 函数                 | 原因                                            |
| ------------------ | --------------------------------------------- |
| `proc.c/do_exit`   | 用户进程退出，放弃CPU资源                                |
| `proc.c/do_wait`   | 用户进程等待，放弃CPU资源                                |
| `proc.c/init_main` | `init`线程会等待所有的用户线程执行完毕，之后调用`kswapd`内核线程回收内存资源 |
| `proc.c/cpu_idle`  | `idle`线程等待处于就绪态的线程，如果有就调用`schedule`           |
| `sync.c/lock`      | 如果获取锁失败就进入等待                                  |
| `trap.c/trap`      | 用户进程在被中断打断后，内核会检查是否需要调度，如果是则调用调度器进行调度         |

当用户进程A发生中断或系统调用之后，首先其中断帧会被保存，CPU进入内核态，执行中断处理函数。在执行完毕中断处理函数后，操作系统检查当前进程是否需要调度，如果需要，就把当前的进程状态保存，switch到另一个进程B中。注意在执行上面的操作的时候，进程A处于内核态，类似的，调度后我们到达的是进程B的内核态。进程B从系统调用中返回，继续执行。如果进程B在中断或系统调用中被调度，控制权可能转交给进程A的内核态，这样进程A从内核态返回后就可以继续执行之前的代码了。
