设计原则
一对一线程模型与多对多线程模型
-
一对一线程模型映射每个用户线程到一个内核线程,每个内核线程都有独立的内核栈,这样的设计是为了能够在一个用户线程执行阻塞系统调用时(比如读磁盘)将内核态上下文保存在自己的内核栈上从而调度另一用户线程执行,以此提高了内核的运行效率。Linux,Windows操作系统家族都采用了这种线程模型。这样做的缺点是由于用户线程的数量很多,系统往往必须限制内核线程的数量或者减小内核线程栈的大小。而且内核线程栈的上下文切换带来了一定的开销。
-
多对多线程模型复用多个用户线程到同样或更少数量的内核线程。同样允许在一个用户线程阻塞系统调用时调度另一个用户线程。
实际上线程模型本质上是解决阻塞式系统调用时线程内核态现场如何保存的问题。Rust的无栈协程机制为内核线程模型设计带来了一种新的解决方案:
-
用户线程执行阻塞系统调用时可以创建一个协程并直接返回,并将自己设置为异步等待状态,这时内核调度器不会再调度此线程。
-
内核中的协程执行器会不断执行所有的协程,当协程执行完毕后,唤醒等待状态的内核线程,此时他们可以被调度器调度。
在上面的过程中好像并没有涉及到线程内核态上下文的保存。但实际上原本保存在内核栈上的上下文被Rust编译器保存在了协程的上下文里(在内核堆上)。如此我们便避免了使用内核线程。实际上Rust协程的上下文切换开销要小于使用内核栈的开销。所以总结起来,使用Rust异步协程有以下两个好处:
- 节省空间,不必为用户线程创建独立的内核栈
- 一定程度上避免了上下文切换开销
在内核设计中首次使用Rust的异步协程机制的是由清华大学的zcore项目,我们的协程执行器和协程设计参考借鉴了zcore的实现。
内核线程设计
在引言中中我们简要地讨论了宏内核和微内核设计模式的优劣:
-
宏内核由于紧耦合,存在系统易崩溃、难以维护拓展的问题,但性能极好
-
微内核牺牲了部分性能来实现安全性和高度模块化
希望平衡内核性能与安全性,我们选择了一个折衷的方案:混合内核:
-
类似微内核将非核心的内核服务运行在独立的内核线程(内核态)中。每个内核线程保持独立性,有自己的内核栈,每个内核线程完成一个特定的服务。用户线程通过系统调用的方式来请求内核线程服务。若某个内核线程出现错误,内核不会崩溃,而可以重启内核线程来重新服务。
-
不同于微内核将多种服务放在用户态,将其运行在内核线程中的好处是内核线程共享内核地址空间,从而可以直接通信而不需要频繁的IPC。
我们将在后面的各章节中逐渐地具体说明上面两个原则的实现与好处。