关注科技新闻的朋友们可能听说过:Google 这几年正在悄悄地开发一个全新的操作系统——Fuchsia,据说是要取代 Android 成为下一代移动端的底层 OS。这个项目如此神秘,以至于到现在都几乎找不到任何官方宣告。不过令人欣慰的是,项目从始至终都是开源的,去年还低调上线了开发者网站 fuchsia.dev(虽然需要科学上网),这就让感兴趣的同学有机会一览其全貌。根据 Fuchsia 中文社区网站汇总的种种消息推测,Google 很有可能在 2020 年初步完成 Fuchsia 的开发,并对外正式发布。网站上还很贴心地挂了一个倒计时,其截止日期正是 Google I/O 2020 举办的日子!随着日期一天天的临近,我们对这个神秘 OS 愈发充满了期待……

然而受到突如其来的疫情影响,今年的 Google I/O 被正式取消。我们近期可能无缘见到这个项目的面世了。

不过大家不用沮丧,还有一个好消息:在大约倒计时 100 天时,我们也启动了一个神秘项目,尝试用 Rust 语言重新实现 Fuchsia 的微内核——Zircon。在此之前,我们已经有过用 Rust 写操作系统——rCore 的经验。经过 100 天的密集开发,赶在官方发布前,我们已经成功山寨了一个 Zircon 内核出来,目前能够正常运行 shell 等基础程序,总代码量仅为 1 万行左右。根据传统,我们将其定名为 zCore。zCore 继承并改进了 rCore 项目中使用 Rust 语言编写 OS 的实践经验,我们希望将它定位为继 rCore 之后的下一代 Rust OS。欢迎所有对 Rust 语言或者 Fuchsia OS 底层实现感兴趣的同学前来围观!

rcore-os/zCoregithub.com下一代 Rust OS:zCore 正式发布_java

下一代 Rust OS:zCore 正式发布_java_02zCore 仓库主页

接下来,我会向大家介绍 zCore 中的一些全新特性,展示 Rust 语言及其生态如何重塑 OS 的开发体验。这种改变并不是一蹴而就的,我们经过了两年的摸索,从 uCore 到 rCore 再到 zCore,不断地添砖加瓦、一次次地推翻重构,汇集了大量优秀同学们的脑洞和汗水。开发 zCore 的过程跌宕起伏,正如一年前开发 rCore 一样,充满了挑战和乐趣。这些体验是如此的独特,我也忍不住想和大家分享一下。

不过由于篇幅所限,后面的故事以后再讲,今天就只介绍一下 zCore 吧!

zCore:下一代 Rust OS

zCore 是用 Rust 语言重新实现的 Zircon 微内核。

它运行在内核态,对外提供与 Zircon 完全相同的系统调用,因此能够运行原生的 Fuchsia 用户程序

下一代 Rust OS:zCore 正式发布_java_03

在 QEMU 中运行 zCore

不仅如此,它还可以作为一个普通的用户进程运行在 Linux 或 macOS 的用户态,我们一般把这种模式称为 LibOS 或 User-Mode OS。你甚至无需安装 QEMU 模拟器,只需装上 Rust 官方工具链,就可以编译运行体验 zCore!

git clone https://github.com/rcore-os/zCore
cd zCore
git lfs pull
cargo run --release -p zircon-loader prebuilt/zircon

既然可以用户态运行,那么它其实就是一个普通的用户程序。这带来了巨大的好处:我们可以在用户态开发,用 gdb 配合 IDE 调试,用 cargo test 跑单元测试,统计测试覆盖率……这在之前的内核开发中是难以想象的。

下一代 Rust OS:zCore 正式发布_java_04coverall.io 上的代码覆盖报告。有些测试没跑,所以看起来比较惨淡。

zCore 的前辈是 rCore。rCore 是用 Rust 重新实现的 Linux 内核,诞生于 2018 年,目前已在清华计算机系的操作系统教学实验中试点应用。(欢迎围观!)

zCore 作为 rCore 的继承者,它并没有把前辈丢掉。事实上,zCore 并不是一个独立的 OS,在它的仓库里还藏着一个小 rCore!只需使用以下命令,即可快速把它召唤出来,我们来运行一个原生 Linux 程序——Busybox:

make rootfs
cargo run --release -p linux-loader /bin/busybox

下一代 Rust OS:zCore 正式发布_java_05

这里面的奥秘在于,Zircon 作为微内核,其实已经提供了内核中最关键的内存管理和进程管理的功能。我们只需在它基础上补充 Linux 作为宏内核的其它功能(例如文件系统),并对外提供 Linux 系统调用接口,即可重新构造出一个新的 rCore。

整体架构

至此,我们展示了 zCore 的各种形态。整个项目的完整架构如下图所示:

每个方块为一个模块(Rust crate),箭头表示依赖关系,竖线表示组合方式。通过不同的组合方式即可组装成不同的 OS。

为了让 zCore 能够同时运行在内核态和用户态,我们在最下面设计了一个硬件抽象层(HAL),将内核所依赖的底层操作封装起来,在裸机环境和 Linux/macOS 环境上分别提供不同的实现。在 HAL 之上的核心是 zircon-object,也就是 Zircon 内核对象,这里面包含了所有内核机制的实现。在对象层之上是系统调用层,它负责将内核对象的功能封装成 Zircon syscall ABI 暴露给用户进程。再往上就是整个 OS 的顶层模块,它负责完成系统初始化和加载第一个用户进程的工作,并将所有模块组装到一起,生成一个可执行文件。

项目规模

经过粗糙的统计,目前各个模块的代码量大致如下:

下一代 Rust OS:zCore 正式发布_java_06zCore 各模块代码量汇总。使用 loc 工具,仅统计 code。

可以看出整个项目还是比较精简的,只用了 1w 多行就实现了两个 OS,并且还能在两种模式下跑。这一方面是因为 Zircon 微内核本身足够简单(相对地,用户程序就十分复杂了),另一方面是因为 Linux 那边也最大程度复用了 Zircon 内核对象的功能。当然这里也有一点作弊,因为有些功能是作为外部依赖引入的,没有统计进来(比如 rCore 的文件系统部分)。

完成度

zCore 麻雀虽小,五脏俱全。目前已经能够在进入 shell 的基础上运行各种小程序。当然由于是微内核,Zircon 在进入 shell 之前已经走过了一段漫长的路程,从 userboot -> bootsvc -> component_manager -> driver_manager,启动了各种服务进程,也用到了各种系统机制。

Zircon 一共有 160 多个系统调用,为了运行到 shell,我们实现了其中的 65 个。(顺便一提,个人认为 Zircon 内核对象的设计十分优雅,功能划分清晰合理,值得学习!)

此外,Fuchsia 官方还提供了很多用户态测试程序,大大提高了我们测试和完善内核的效率。目前我们已经能够通过约一半的测试,剩下的还在逐步完善中。

下一代 Rust OS:zCore 正式发布_java_07zCore 测试状态表:https://github.com/rcore-os/zCore/wiki/Status:-Core-tests

语言分析

在这种状态下,我们可以把 zCore 和官方的 Zircon 做一个对比:

下一代 Rust OS:zCore 正式发布_java_08下一代 Rust OS:zCore 正式发布_java_09

官方 Zircon 是用 C++ 语言编写的,代码量约有 10w 行。而 zCore 只用了 1w 行 Rust 就实现了其中大部分核心功能。虽然我们还差一些没有实现,但相差一个数量级的规模还是让我感到有些诧异。不过至少据我观察,C++ 的 Zircon 代码从设计上就比较复杂,用了各种自己造的轮子,并且充斥着魔法操作。相比之下,Rust 的 zCore 代码看起来更加自然,核心库自带的基础设施再加上一些社区库的辅助,用起来还是非常舒服的。

关于 Rust 大家更关心的另一个话题是 unsafe。在 zCore 中我们尽量避免了 unsafe 的使用,但没有绝对禁止(毕竟禁止就写不出来了)。据统计,在 HAL 之上大约有 20 个 unsafe,其中大部分用在了两个对象之间互相取 Weak 引用的操作,剩下的也比较容易检验正确性。而 HAL 之下 unsafe 就比较多了,由于贴近底层硬件,几乎处处 unsafe,也就跟 C 没什么区别了。不过好在 HAL 代码还是比较少的,不过几百行而已。

对于 Rust,Fuchsia 官方上个月发布的一份语言政策(知乎上也有所讨论)中明确表示,不会将 Rust 用于编写内核,原因是内核要使用工业级别的稳定技术:

Rust is approved for use throughout the Fuchsia Platform Source Tree, with the following exceptions:
kernel. The Zircon kernel is built using a restricted set of technologies that have established industry track records of being used in production operating systems.

而我们希望通过 zCore 的实践表明:Rust 是适合编写内核级代码的,并且比 C/C++ 更能胜任这一任务。

Rust 唯一的问题就是门槛太高了。然而对于编写内核这种对性能、稳定性、安全性都要求极高的程序而言,门槛高一点未必是坏处。在被 Rust 编译器反复教做人之后,才知道自己当初太天真,写出来的程序处处是隐患。

async 机制

除了上面提到的用户态运行之外,zCore 还有一大创新之处:首次在内核中引入了 async 无栈协程机制

熟悉主流编程语言的朋友会知道,async 是近几年开始流行的一种语言特性,能够让开发者用同步的风格编写异步代码。它本质上是将代码变换成状态机,在 OS 线程的基础上又提供了一层轻量级的“协程”,使得程序能够高效处理异步 IO,同时保持开发的高效率。

Rust 语言于 2019 年底正式稳定了 async-await 语法,并于今年 3 月份的 PR#69033 中为 no_std 环境下使用 async 扫清了障碍。这使得在内核中全面应用 async 机制成为了可能,而 zCore 可能是第一个吃螃蟹的人。(C++20 中也引入了同样的特性,不过考虑到历史包袱和生态问题,我比较怀疑能否真正用起来)

在传统 OS 中,每个内核线程需要有自己独立的内核栈。当线程挂起时,它的状态就保存在栈上。由于内核线程可能很多,因此每个线程的栈都不能太大,在 Linux 中一般是两个页也就是 8KB。而在 zCore 中,所有内核线程都变成了协程,在一个 CPU 核上共享同一个内核栈。当进入用户态时,内核栈不再清空,因为要保留必要的信息,于是内核-用户切换的风格从传统的「用户态中断调用内核处理函数」变成了「内核主动调用函数切换到用户态执行」。当任务挂起时,协程的状态被包装成 Future 存储在堆上。根据计算,目前每个 Future 状态机的大小约为 600B 左右,大幅节省了内存空间。

无栈协程相比线程的好处除了空间占用少以外,还有更小的上下文切换开销,进而实现更高的并发和吞吐率。不过它的缺点在于协作式、不可抢占,这可能会为系统的实时性带来挑战。关于二者之间的对比,还有待进一步的测试和分析。

总结

zCore 的主要特性和创新点:

  • 第一个完全山寨的 Zircon 内核

  • 使用 Rust 编写,实现精简,层级清晰

  • 支持用户态开发、测试和运行

  • 第一个在内核中使用 async 机制

(这些号称的“第一个”都是据我了解,如有错误欢迎指正)

总的来说,zCore 应该是目前为止我们能想到、做到的,Rust 语言操作系统的集大成之作了。