LWN: kernel 快要有 Rust 了?

Linux News搬运工

共 6224字,需浏览 13分钟

 ·

2021-05-14 01:40

关注了就能看到更多这么棒的文章哦~

Rust heads into the kernel?

By Jake Edge
April 21, 2021
DeepL assisted translation
https://lwn.net/Articles/853423/

在给 linux-kernel 邮件列表的一封长长的邮件中,Miguel Ojeda "介绍" 了 Rust for Linux 这个项目。很可能大多数内核开发者之前已经听说过这项工作了。例如 2020 年的 Linux Plumbers 会议上就对该项目进行了广泛的讨论。以前也在列表中被提出来过。现在,该项目正在询问内核社区对其计划有些什么反馈,因此在 4 月 14 日的时候发布了 RFC。

Adding Rust

Ojeda 首先很老实地表态说,他也理解在内核中新增另一种语言总会有一定的破坏性,所以需要有很好的理由才会这样做。内核已经是一个高度复杂的代码综合产物,对大家理解代码和修改代码都增加了不少困难。要在其中加入第二种语言以及其相关的复杂问题,只会使情况变得更糟。"尽管如此,我们相信,即使就在眼下,如果我们使用 Rust 的话,优势也是超过成本的。"

这些好处主要来自于 Rust 语言的内存安全(memory-safety)特性。我们希望通过避免这些问题来减少内核中的 bug 数量(至少是那些用 Rust 语言实现的部分代码)。目前,对 Rust 实现的代码的定位仍是远离核心内核(core kernel)代码:

请注意,至少在可预见的未来,支持 Rust 的目的是让人们能够用 Rust 编写驱动和类似的 "leaf" 模块。具体来说,我们不打算重写内核核心或那些主要的子系统(例如,`kernel/`,`mm/`,`sched/`…)。相反,Rust 的支持是会建立在这些基础之上。

内核 C 代码和 Rust 之间有很多不匹配的地方,需要一些方式来处理好。为了从 Rust 的安全特性中获得最大的好处,那就需要尽量减少不安全的部分中的起支持作用的内核代码的数量,并仔细记录在文档中。其中一个既定的目标就是要能采用自动机制来确保执行文档记录原则:

通过利用 Rust 现有工具,我们在这个项目中一直在确保执行迄今为止建立好的所有文档准则。例如,我们要求对所有公共 API、安全前提条件(safety preconditions)、"unsafe" block 和类型不变性(type invariants) 进行记录。

到目前为止,这些工作主要集中在实现底层组件(building blocks)和开始对内核 API 进行抽象和封装,但还有很多工作需要去做:"要想覆盖整个内核的 API 接口,这会需要很长的时间来开发稳定。" RFC 中重申了这个任务,但同时也指出有一些工具可以对这个过程有所帮助:

[……]用 Rust 编写的模块不应该直接使用 C kernel API。在内核中使用 Rust 的根本意义就是我们要开发出安全的抽象层,使这些 module 更加容易模块更容易推理,因此,审查、重构等等。

此外,内核的 C 这一侧的绑定(binding)是通过`bindgen`(官方的 Rust 工具)即时(on-the-fly)生成的。我们使用这个工具,就可以避免在 Rust 这一侧更新 binding。

在谷歌安全博客(Google security blog)的一篇文章中(这篇文章在 LWN 发布时引起了大量的评论),Linux 的 Rust 维护者之一 Wedson Almeida Filho 详细描述了 Ojeda 的 RFC 中一个示例驱动程序。这是一个实现 semaphore 的字符设备,主要用于演示的目的。RFC 还包含了一个重新实现过 Android Binder 进程间通信机制,尽管这个尚未完成,但它让我们进一步了解了在内核中使用 Rust 可能会有哪些用处:

目前,我们几乎将 Binder 所需的所有那些通用内核功能都干净利落地封装在安全的 Rust 抽象中了[…] 。

我们还将继续推进修改 Binder 这个原型,来实现更多的抽象,并处理好一些目前处理得比较粗糙的地方。这是一个激动人心的时刻,也是一个难得的机会,很可能会影响到 Linux 内核的开发,同时也可以有利于 Rust 语言的发展。

RFC 指出,目前的 Rust 支持代码在内核已经实现了相当多的代码,但随着时间的推移,会逐步缩减这些改动。在完全支持 Rust 的情况下,"我们在 CI 中使用的小型 x86_64 环境" 的内核 size 增加了大约 4%。Rust 版本的 semaphore 驱动比 C 版本的驱动大 50%左右,而 Binder 驱动的 size 基本上保持相当。然而,"请注意,虽然 Rust 版本尚未完全实现 C 语言的原始模块中的全部功能,但已经足够接近了,可以作为粗略的估计之用"。

Reaction

总的来说,人们对 RFC 的反响还是不错的,尽管有一些例外。当然对现有代码也有一些疑问和担忧。Linus Torvalds 似乎专注于这些代码中调用的 BUG(),很明显,他看到这些时感到很不赞同。内核哪怕面对 error 时会努力继续往下走,但碰到 BUG() 调用的话就会直接放弃,并且打印出 backtrace 以及触发 kernel crash。其中有一个例子里,Rust 标准库中的一些 intrinsic 操作("panicking intrinsics")是内核所不支持的,只要调用这些操作,就可以通过触发 BUG() 从而马上让内核崩溃。Torvalds 建议在编译的时候就让这些调用直接报错,Ojeda 同意这是一个更好的方法,但也指出,随着时间的推移,更多的标准库(standard library)会被删除,很可能这些问题也会被消灭。

Torvalds 还指出了内存分配代码中的 panic!()调用,这在他看来是 "根本性的错误":

如果 Rust 编译器偷偷进行了分配(allocation),然后会出现 panic,那么我们加入 Rust 的一个主要好处就不复存在了。这跟 build 时候的 memory-safe 要求是背道而驰的。

随便在哪个驱动程序中,分配内存失败绝不可以是编译器触发 panic 的理由。它必须被捕捉下来并且进行同步处理(handled synchronously),返回一个 ENOMEM 错误。

Ojeda 再次表示同意。他指出,要调整好 Rust 的标准库供内核使用,还有很多工作要做:

这里的情况是,我们暂时使用 'alloc',它是 Rust 标准库的一部分。然而,我们后续会根据需要定制、重写 'alloc',从而指定其类型(如'Box'、'Vec'等),这样我们就可以用来做一些其他工作,比如传递一个 allocation flag,确保我们总是可以支持这些会出错的 allocation,或者可以重用一些内核数据结构,等等。

用 Binder 驱动作为例子,这是 Torvalds 比较关心的另一点。他希望看到一个 "真正的代码的例子,是真正在做一些有意义的事"。Ojeda 说,计划会增加一些真正同硬件交互的驱动程序。Matthew Wilcox 有一个想法,可以从哪里开始:

我建议把 NVMe 作为一个目标。它是现成的,真实硬件和 qemu 等虚拟环境中都有支持。它的 spec 也是免费提供的,而且大多数设备的实现都非常接近 spec,除非你要追求一些细节。此外,你还可以做性能测试,看看可以对于性能在哪些方面来集中优化。

Greg Kroah-Hartman 同意 Torvalds 的观点,认为 Binder 并不是一个特别好的驱动实例,但他对该项目到目前为止所取得的成就印象深刻:

[……]这组 patch set 是一个伟大的开始,它提供了 "如何在内核构建系统中构建 rust" 的最主要部分的展示,这是一项非同小可的工程。向他们致敬,我所要做的工作就是仅仅在我的系统上成功安装合适的 rust 编译器(这不是这些开发者的错),然后就成功地编译了内核代码。这是一个重大的成就。

他还认为 NVMe 可能是一个不错的选择,但也建议试试 "驱动作者每天都要处理的一些基本问题(platform driver、gpio driver、pcspkr driver、替换/dev/zero 等)" 。看来 Rust for Linux 项目将在不久之后致力于开发一些符合这种期望的 "真正的" 驱动程序。

虽然 Torvalds 重申了他对一些个别 patch 的抱怨,但他也说。"总的来说,我并不讨厌这组 patch"。但另一方面,Peter Zijlstra 似乎从根本上反对在内核中增加第二种实现语言的想法。RFC 指出,内核工具一直专注于 C 语言,"包括 compiler plugins、sanitizers、Coccinelle、lockdep、sparse",但是 "如果 Rust 在内核中的使用随着时间的推移而增加,Rust 相关的工具可能会得到改善"。Zijlstra 对此进行了归纳,并问道:

在重构的时候,我们可以不受限制地移除这些 .rs 内容吗?如果我们不能在没有 Rust 这堆东西的情况下启动 x86_64,那么情况会怎样?

我们可以把这个问题当作未来的问题来暂时忽略,但我认为现在讨论这个问题才是合理的。我真的不关心未来,而且在我看来,在内核中加入 Rust 或其他任何第二语言都会失败。

也许不出意料,他对项目使用的文档格式(代码中的 Markdown 转换为 HTML)强烈反对。他对所使用的代码格式也不满意,根据 RFC 的说法,至少目前还是遵循 "Rust 的习惯性风格",但 "真的非常非常难读"。除了这些,他还想知道 Rust 遵循什么内存模型,以及它与 Linux 内核内存模型(LKMM,Linux kernel memory model)如何 "保持一致(或干脆就不一致?)"。

Memory model

Boqun Feng 说,Rust 目前使用 C11 内存模型,主要是因为 LLVM 编译器默认支持它,但人们很想知道其内存模型与内核的内存模型是否能很好配合。现在,"在 C 这一侧和 Rust 这一侧之间没有需要保持同步的代码,所以我们目前没有问题",但这个情况最终会发生变化的,所以有计划让 Rust 和内核中相关的人员一起讨论这个问题。Almeida 指出,最终希望是让内核中的大多数 Rust 代码只需要关注 Rust 的内存模型:

我们不打算将 C 语言的数据结构直接暴露给那些(在 kernel crate 之外的) Rust 代码。相反,我们打算提供封装函数,对外暴露安全接口,即使底层的实现可能会使用了 unsafe block。所以我们希望绝大多数的 Rust 代码只关心 Rust 的内存模型。

我们承认还没有实现很多封装函数,但我们目前的已经足够实现 Binder 的主要功能了,到目前为止还不错。我们确实打算最终也能够处理其他类别的驱动,这些驱动可能会向我们展示出一些此前未预见到的困难,我们慢慢来吧。

Almeida 不同意 Zijlstra 关于 HTML 是一种无效的文档格式的说法,他认为这是一种个人偏好。对于代码格式,如果有很好的理由,他不反对脱离 Rust 风格,但认为 Zijlstra 的批评没有说服力。" '在 if-clause 表达式周围没有小括号完全是垃圾' 这种说法对我来说听起来并不是一个好的理由。"

Al Viro 试图解释对 HTML 文档的厌恶,语气非常的直接,这使争论暂时偏离了轨道。Zijlstra 说,没有好方法来使用 ASCII 阅读 HTML 文档:"没有什么是一个合理的 ASCII 文档不能完成的,有必要的话可以再加上一些 ASCII art 图。" 他还解释了为什么他对格式的看似随意的抱怨实际上很重要:

当然有关系;我大脑里的词法分析一直在对我大喊 '这里有语法错误',当我不能理智地阅读代码时,我怎么能理解它呢?

你越是让它看起来像(内核里的)C 代码,我们这些 C 语言的人员就越容易真正读懂它。我的眼睛已经读了近 30 年的 C 语言了,就好象内置在视神经中有个相应的词法分析器,阅读那些看起来隐约像 C 语言但绝对不是 C 语言的东西是一种痛苦的经历。

你是在要求加入我们,而不是反过来。我在没有 Rust 的世界里过得很好。

Zijlstra 还提出,许多被吹捧的 Rust 功能都可以在 C 语言中实现。Almeida 同意确实是可以实现的,但 Rust 的价值是人们不可能错误地忽略这些功能,不像 C 语言(至少在不改变编译器的情况下)那样:

在 Rust 中,这是不可能的:受锁保护的数据只有在获取锁之后才能被访问。所以开发者不可能意外地犯这样的错误。而且这是在编译时确保的,因此在运行时没有额外开销。

他还提出了 C 语言中的 ownership 问题:在 C 语言中没有办法转移一个对象的 ownership,但在 Rust 中却可以直接做到:

在 Rust 中,有一种干净的现成方式来转交一个 guard(或任何什么 object)的 ownership,这样在所有权转移后,以前的所有者就不能继续使用它了。同样,这也是在编译时确保的。

但是 Zijlstra 宁愿看到一个支持所有权的 C extension,而不是将 Rust 添加到内核中:

这就意味着我们要比以前更积极地推动新的 C 语言编译器,但至少我们仍是使用单一一种语言。对新东西的转换可以逐步进行,并且在有意义的地方进行,新的 extension 可以针对性地评估性能影响。

Almeida 并不反对这个想法,事实上,恰恰相反:

我鼓励你继续这样做。我们都会从更好的 C 语言中受益。我很乐意对那些被认为等同于或者优于 Rust 的 extension 进行 review 并提供反馈。

我的背景也是 C 语言。我不是 Rust 的粉丝,我只是以我认为的实用主义的观点来看待现有的选项。

然而,有点难以想象内核会因为一个尚不存在的 C extension 而停止继续调查是否能将 Rust 加入内核中。但是还有很多工作需要 Rust for Linux 来完成,其中一些工作很可能需要先完成,之后 Torvalds 才会愿意 merge。例如,似乎需要更多的 "real" driver 实例,以及删除那些导致 BUG()调用的代码。但是,Rust for Linux 显然越来越接近于成为现实。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~



浏览 26
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报