Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

线程模型

在支持多线程的内核中,存在内核线程与用户线程两种线程对象,用户线程位于内核线程之上,它的管理无需内核支持,而内核线程由操作系统来直接支持与管理,只有内核线程才能通过操作系统调度以便运行于物理处理器。本节首先介绍常用的一对一和多对多线程模型设计。

一对一线程模型

一对一模型映射每个用户线程到一个内核线程,一个用户线程阻塞系统调用时,将内核现场保存在自己的内核栈上,并调度另一个线程继续运行。这种模型允许多个线程并行运行在多处理器系统上,唯一的缺点是,创建一个用户线程就必须创建一个相对应的内核线程,由于创建内核线程的开销会影响应用程序的性能而且每个内核线程都有独立的内核栈,所以这种模型的实现大多数限制了系统支持的线程数量以避免过大的性能和内存开销。Linux系统和Windows操作系统家族都实现了这种模型。

多对多线程模型

多对多模型多路复用多个用户线程到同样数量或更少数量的内核线程。多对多线程模型不存在一对一模型的限制,系统可以创建任意数量的用户线程,并且相应的内核线程可以在多处理器系统上并发执行,且一个线程执行阻塞系统调用时可以调度另一个线程来执行。

实际上线程模型本质上是解决阻塞式系统调用时线程内核态现场如何保存的问题。Rust的无栈协程机制为内核线程模型设计带来了一种新的解决方案:

  • 用户线程执行阻塞系统调用时可以创建一个协程并直接返回,并将自己设置为异步等待状态,这时内核调度器不会再调度此线程。

  • 内核中的协程执行器会不断执行所有的协程,当协程执行完毕后,唤醒等待状态的内核线程,此时他们可以被调度器调度。

在上面的过程中好像并没有涉及到线程内核态上下文的保存。但实际上原本保存在内核栈上的上下文被Rust编译器保存在了协程的上下文里(在内核堆上)。如此我们便避免了使用内核线程。实际上Rust协程的上下文切换开销要小于使用内核栈的开销。所以总结起来,使用Rust异步协程有以下两个好处:

  • 节省空间,不必为用户线程创建独立的内核栈
  • 一定程度上避免了上下文切换开销

在内核设计中首次使用Rust的异步协程机制的是由清华大学的zcore项目,我们的协程执行器和协程设计参考借鉴了zcore的实现。

内核线程设计

在引言中中我们简要地讨论了宏内核和微内核设计模式的优劣:

  • 宏内核由于紧耦合,存在系统易崩溃、难以维护拓展的问题,但性能极好

  • 微内核牺牲了部分性能来实现安全性和高度模块化

希望平衡内核性能与安全性,我们选择了一个折衷的方案:混合内核:

  • 类似微内核将非核心的内核服务运行在独立的内核线程(内核态)中。每个内核线程保持独立性,有自己的内核栈,每个内核线程完成一个特定的服务。用户线程通过系统调用的方式来请求内核线程服务。若某个内核线程出现错误,内核不会崩溃,而可以重启内核线程来重新服务。

  • 不同于微内核将多种服务放在用户态,将其运行在内核线程中的好处是内核线程共享内核地址空间,从而可以直接通信而不需要频繁的IPC。

我们将在后面的各章节中逐渐地具体说明上面两个原则的实现与好处。