[译]2021.4.14_Linux内核引入Rust支持的RFC
内核中的第二语言
邮件原文:https://lkml.org/lkml/2021/4/14/1023
众所周知,在内核中引入一种新语言有巨大成本和风险。因为这会分散精力,也会提升对内核某些部分的知识门槛。
最重要的是,引入新语言之后,如果该语言不再被支持了,那用这门语言写的模块之后很难被替换。
无论如何,我们相信,即使在今天,使用Rust也是利大于弊。这些后面会解释。
请注意,我们说的Rust支持,指的是支持用Rust写驱动以及类似的“叶”模块,至少在可见的将来是这样。特别地,并不是要重写内核的核心代码,也不是要重写内核的重要子系统(比如kernel/mm
、sched/
,等等)。对Rust的支持会建立在这些之上。
目标
通过在Linux内核中使用Rust,我们希望达成如下目标。
- 用Rust写的新代码能降低内存安全隐患、数据争用和整体的逻辑错误,这些是由下面提到的语言特性决定的。
- 维护者更有信心重构和接收对模块的补丁,这利益于下面提到的Rust安全能力。
- 新驱动和模块写起来会更容易,因为Rust基于现代语言特性的抽象更容易推断,而且有详尽的文档可供查阅。
- 更多人可以参与内核开发,因为使用现代语言的人更多。
- 利用Rust工具生态的优势,可以强制保证之前我们制定的文档规范落地。比如,要求所有公共API、安全前置条件、
unsafe
块和类型不变量都必须有文档说明。
为什么选Rust
Rust是系统编程语言,就Linux内核而言,拥有几个C所不具备的重要优势。
- 使用其安全特性时不存在未定义行为(when unsafe code is sound),包括内存安全和不存在数据急用。
- 类型系统更严格,进一步减少逻辑错误。
- 可以清晰地区分安全与
unsafe
代码。 - 特性丰富:sum类型、模式匹配、泛型、RAII、生命期、共享&专有引用、模块&可见性、强大的清洁能力(hygienic),以及过程式的宏,等等。
- 大量独立的标准库:
Result
和Option
等词表类型、迭代器、格式化、受控/丰富/包装的整数计算,等等。 - 开箱即用的工具,如文档生成器、格式化、代码纠错,全部内置在编译器中。
总的来说,Rust这门语言成功吸引了数十年来系统编程乃至函数式编程的经验,而且增加了生命期和借用检查机制。
为什么不用
相对于C,就Linux内核而言,Rust也有一些不足。
- 多年来围绕内核投入了非常多的人力开发C工具,包括编译器插件、坏码检查(sanitizer)、Coccinelle、lockdep、sparse等。不过,随着Rust在内核上的应用得越来越多,这些问题有可能改善。
- 基于LLVM的唯一实现。有人正着手解决这个问题,比如GCC前端、基于Cranelift的
rustc
后端和旨在缩短自举流程的编译器mrustc
。欢迎对任何这些项目提供帮助。 - 没有标准化。尽管还不清楚标准化是否对内核有益,但有几点可以把风险降到最低:Rust稳定性承诺、丰富的文档、WIP参考,还有详尽的RFC,等等。
- 编译通常比较慢,一方面语言特性比较复杂,另一方面当前的编译器也施加了诸多限制。
- 目前,还需要一些最新的特性。换句话说,就是稳定版编译器还不支持的特性。无论如何,我们计划在一年内扫清这些限制,要么让
rustc
稳定版支持它们,要么在我们的代码中去掉相关特性。这里有我们维护的一个报告:https://github.com/Rust-for-Linux/linux/issues/2。 - 当前文本量比预期大,原因是
core
和alloc
这两个Rust标准库里有一些未用到的部分。我们计划逐步解决这个问题。
上述问题多数缘于Rust还是一个相对年轻、应用较少的语言。不过,相信Rust很有希望成为系统编程大家庭中的重要成员,就像过去几十年的C一样。因此,多数上述问题都会随着工业界将更多资源倾注于Rust而缓解。
设计
有几个重要的设计决定。
首先,Rust内核模块要求共享一些代码,可以通过一个配置项生效(CONFIG_RUST
)。这样可以让个别模块更小。对Rust的支持包括:
- Rust标准库。目前是
core
和alloc
,未来可能只需要core
的子集。这些基本上相当于C标准库的那些独立子集。 - 封装内核API的抽象。代码位于
rust/kernel
。目的是让它们尽可能安全,因而让用Rust写的模块尽可能少地依赖unsafe
代码。 - 其他,比如
module!
过程式宏、编译器内置能力、生成的绑定及辅助函数,等等。
支持Rust占据比较大的空间,当然把Rust标准库中那些未用代码删掉会变小。
以下是我们在CI中使用一个小x86_64配置的例子:
text data bss dec
7464833 1492128 2301996 11258957 vmlinux (without Rust support)
7682527 1709252 2301996 11693775 vmlinux (with Rust support)
7682527 1721540 2301996 11706063 vmlinux (plus overflow checks)
2224 0 16 2240 samples/rust/rust_semaphore_c.o
3694 0 10 3704 samples/rust/rust_semaphore.o
2367 768 16 3151 samples/rust/rust_semaphore_c.ko
3829 768 10 4607 samples/rust/rust_semaphore.ko
80554 5904 20249 106707 drivers/android/binder.o
12365 1240 9 13614 drivers/android/binder_alloc.o
92818 8 16 92842 drivers/android/rust_binder.o
在启动溢出检查的vmlinux中,文本(text)多出3%,整体多出4%。模块本身与对应的C代码接近。
上表也比较了Binder和Rust对应的原型实现。注意,虽然Rust版还达不到原始模块的要求,但可以进行大致的估计。如表所见,C Binder的文本总和少于Rust驱动,但加一块更大。
其次,Rust写的模块永远都不应该使用C内核API。因为在内核中使用Rust最主要的目的是提供安全抽象,让模块更容易推断,进而更容易评审、重构,等等。
另外,与内核C端的绑定是通过bindgen
(Rust官方工具)动态生成的。这样可以不用在Rust端更新绑定。
宏还是需要手工处理,有些函数会被行内化,对此我们要创建辅助函数从Rust里调用。
第三,在Rust代码中,多数文档都是跟源代码写在一块的,格式为Markdown。我们也遵循了这个惯例,因而虽然一些通用文档在Documentation/rust/
里,但实际上多数文档都在源代码文件中。
为了方便阅读文档,Rust提供了一个生成HTML文档的工具,类似于Sphinx/kernel-doc,只不过面向的是Rust代码和语言特性。
此外,正如上面解释的,我们也借这个机会推动了文档策略。同时也推动了代码的自动格式化,一套Clippy代码纠错工具。这里面都是按照Rust的惯例来的,比如保持rustfmt
这个默认值。比如,代码缩进是4个空格,而不是制表符宽度。如果需要的话我们也乐意修改,关键在于自动格式化。
最后,为避免将GPL符号暴露为非GPL(即使是间接暴露),我们将Rust支持的所有符号都作为GPL导出到了内核中。
状态
这里展示的Rust支持是试验性的,因此很多内核API及抽象都看不到。要覆盖全部API需要很长时时间开发和成熟。其他实现细节也在进行中。
不过,这个支持已经足够好,可以用来实现模块原型了。本RFC包括对已有模块的可用移植,主要是Binder和Android IPC机制。尽管还不能在产品中使用,但它展示了可以做到哪一步,以及将来的Rust模块可能是什么样的。
说到编译器,我们支持Clang构建的内核,以及可能情况下的LLVM=1
构建(比如,只要ClangBuiltLinux支持)。此外我们也维护了GCC构建内核的配置,不过现在已经不建议使用了。以bindgen
为GCC后端很适合改进对这些构建的支持。
至于架构,我们已经支持了x86_64
、 arm64
和ppc64le
。要支持其他变体如riscv
、s390
和mips
也不会太麻烦。
我们也加入了linux-next
(签署了特别弃权状)。当前,我们的支持是通过!COMPILE_TEST
控制的,因为不想由于错误破坏任何产品CI,但如果对本RFC的反馈是正向的,那我们会去掉这个限制。
上游计划
一如既往,早点介入维护是发现缺失细节的最佳方式,因此我们会在合并窗口一到就发送这些变更。
审阅本RFC
我们希望得到模块作者的意见。特别是关于补丁9的示例和补丁13的Binder。也就是说,身为模块作者,看到那里的Rust代码你会作何感想?你觉得自己将来也会写类似的Rust代码,考虑安全/无UB吗?
对Rust抽象本身及本RFC其他细节的意见我们也欢迎,但请注意这些都在进行中,还没最终完成。
另一个我们希望得到反馈的主题是Rust “原生”文档,即写在代码里的那些注释。已经上传到这里了: https://rust-for-linux.github.io/docs/kernel/。
我们这种生成的文档还挺好的。请大家也看一眼,告诉我们你的想法。
测试本RFC
如果想测试一下,请遵循Documentation/rust/quick-start.rst
中的Quick Start。其中包括如何设置Rust以及其他构建和测试本RFC所需的工具。
本文写作时,本RFC系列与仓库是对应的,但要想及时跟进,请检出我们主干中的rust
分支:https://github.com/Rust-for-Linux/linux.git。
致谢
(略)