LWN 订阅者为您带来的这篇文章 LWN.net 的订阅者使这篇文章——以及围绕它的一切——成为可能。如果您欣赏我们的内容,请购买订阅并让下一组文章成为可能。 7 月 4 日,Rust for Linux 项目发布了另一个版本的补丁集,增加了对内核语言的支持。该项目似乎已经准备好考虑进入主线。不过,也许一个更大的问题仍然存在:内核开发社区是否为 Rust 做好了准备?那部分似乎仍然悬而未决。 Miguel Ojeda 已被聘用全职从事该项目,他发布了补丁集;在求职信中,他列出了自 4 月份发布 RFC 补丁集以来的一些更改和更新。特别是,在失败时调用 panic!() 的分配已被替换。已经为内核项目创建了 alloc crate 的修改版本,尽管计划是让更改进入上游项目,以便最终可以删除自定义版本。顺便说一句,添加修改后的 crate 的补丁显然太大了绝杀档案,尽管它确实出现在 LWN 档案中。他说,在内核设施的 Rust 抽象方面取得了更多进展,包括“红黑树、引用计数对象、文件描述符创建、任务、文件、io 向量……”,以及对驱动程序支持的补充。除此之外,Android Binder 进程间通信机制的 Rust 驱动程序具有更多功能,并且正在开发用于某些 Raspberry Pi 型号上可用的硬件随机数生成器的驱动程序。但是缺乏“真正的”车手是 4 月份的症结之一,这次又被提出来了。 Christoph Hellwig 表示,如果这是发布的意图,他强烈反对合并代码。在求职信中,Ojeda 说补丁集正在添加到 linux-next,并且他确认正在提交合并。 Hellwig 希望看到 Rust 在被包含之前证明自己:[...] 首先证明它确实有用。有用的将是像 nvme 这样的真实驱动程序或 USB 主机控制器驱动程序,它实际上可以工作并显示出优于现有驱动程序的优势。在它显示出如此有用之前,它只会拖累其他人。 Ojeda 指出 Binder 驱动程序是一个“非平凡的模块”,“已经在工作”,但承认真正的硬件驱动程序是一个重要的(并且目前还没有)。但是,求职信列出了一些对该项目感兴趣或已经参与其中的组织,Ojeda 认为这也有助于证明该项目:从某种意义上说,我们已经开始让下游用户和其他感兴趣的用户产生兴趣。各方表示支持。然而,其他人则更加保守,只有在我们处于主线时才会开始投资它,即如果已经做出是否使用 Rust 的决定。但是,Greg Kroah-Hartman 说,由于一些不同的原因,Binder“驱动程序”并不是一个很好的例子。它缺少一个相当大的功能(binderfs),但它也几乎没有帮助展示 Rust 将如何与内核的其余部分相适应。和以前一样,他强烈建议做一些有助于解决内核开发人员对 Rust 的一些问题的工作:并不是说它没有用途,而是 Binder 和内核其余部分之间的交互非常小且具体. 几乎没有人会再写的东西。请在一个真正的驱动程序上工作,以帮助证明或反驳这一切都能正常工作。有很多悬而未决的问题需要解决,当你做这样的事情时,你会遇到这些问题。
Rust 证明自己的主题也出现在 ksummit-discuss 邮件列表上的一个线程中。 6 月底,Ojeda 在今年的 Linux PlumbersConference 上提出将 Rust for Linux 作为 Kernel Summit 赛道的技术主题。 7 月 6 日,Linus Walleij 回复,同意这是一个应该讨论的话题。他指出,内核开发人员已经需要关注很多语言(例如 C、汇编、Make、Bash、Perl、Python 等),所以他心中的一个问题是 Rust 将带来什么值得添加到列表中。该消息引发了关于 Rust 如何适合以及实验将走向何方的讨论。内核开发人员显然有些担心必须了解 Rust 才能继续开发内核。Ojeda 试图在一定程度上减轻这些担忧,但承认大多数内核开发人员最终需要了解该语言。 Ojeda 说,目前,该项目的目的是与希望向其子系统添加 Rust API 的维护者合作。这将在内核中引导对 Rust 的支持,并有助于缓解 Leon Romanovsky、JamesBottomley 和其他。 Romanovsky 担心在具有两种语言的驱动程序的系统中进行重构,尤其是跨子系统重构。 Bottomley 建议 Rust 代码的审阅者会更少,所以 bug 更容易溜进来:由于我们现在的大多数 CVE 类型问题通常是编程错误,缺乏审阅可能会导致编程错误类型错误的增加,这些错误不是被禁止的更安全的内存模型。在短期内,这些都是需要解决的问题,但目标是让 Rust 知识在内核开发人员中得到更广泛的传播。正如 Ojeda 所说:毕竟,如果我们要将 Rust 作为内核中的第二语言,我们应该尝试在合理的时间范围内让尽可能多的人加入,至少在某种程度上是这样。正如 Laurent Pinchart 指出的那样,这是一个重要的观点,需要在讨论中更明确地强调:[...] 在内核中采用 Rust 作为第二语言不仅仅是一个影响有限的技术决定,也是一个过程决定这将为大多数内核开发人员学习 Rust 创造条件。这是否应该和将会发生是我们正在辩论的问题,但无论结果如何,正确地表达问题并广泛地了解其含义是很重要的。当然,还有很多技术障碍需要清除。这些领域之一是 Linux 驱动程序模型和随之而来的对象生命周期处理。 Roland Dreier 建议像 devres(即管理设备资源)这样的接口可以避免很多他在驱动程序中看到的关于生命周期管理的问题,特别是错误路径,但它没有被广泛采用。 Walleij 不同意它没有被广泛使用,但完全不确定 Rust 开关是否更好:我认为这是一个巨大的成功,人们只需要学习更多。但是,如果学习更好行为的更简单的方法是洗牌整个棋盘并用 Rust 编写的驱动程序替换它,我不知道?也许?对于 Kroah-Hartman 来说,在 Rust 中看到内核的真实驱动程序将有助于弄清楚这些问题将如何在语言中解决。有一些难题需要解决:这将是“有趣的” Rust 工作的一部分,它必须弄清楚如何将我们当前在驱动程序模型中拥有的引用计数对象映射到 Rust 控制的对象并保持一切正确同步。对于普通代码,内存被分配给一个特定对象(结构设备)但还被另一个对象(cdev)引用的事实。像这样的 devm_* 用户似乎没有意识到这里发生了两个单独的对象生命周期,因为交互有时很微妙。我很期待 Rust 实现将如何处理所有这些,因为我不知道。
还有一个问题是所有这些导致了哪里。 Walleij 想知道内核(即驱动程序)的“叶节点”是否实际上是从展示语言优势的角度出发的最佳位置。他还在 4 月份的长篇消息中询问了这一点。他的部分问题是决定从驱动程序开始是否是出于其他原因:如果首先做设备驱动程序的整个基本原理是战略性的,不一定会给该设备驱动程序子系统带来任何好处,而是作为试验场和试验场,那么该战略需要明确并被每个人理解。因此,虽然我们理解 Rust 作为所有这些 $UPSIDES 首先做设备驱动程序纯粹是战略性的,对吗?我认为也提到了如果它没有解决就可以撤回整个事情的能力?他还询问了用 Rust 编写整个子系统的问题。这将需要将 Rust API 暴露给内核中其他地方的 C 代码。它也有可能允许在内核中向越来越多的 Rust 演进:如果我们想在 Rust 中*编写*一个子系统,那么它当然会以另一种方式:Rust 需要将 API 暴露给 C。我假设宏伟的愿景是在那之后,Rust 将一次吃掉 Linux。如果它证明比 C 更好,那就是。 Ojeda 说,虽然可以将 Rust API 暴露给系统的 C 部分,但他不建议这样做。问题是 C 调用者失去了 Rust 带来的很多好处:一般来说,我会避免将 Rust 子系统暴露给 C。当然,这是可能的,并且它在*实现*中为您提供了 Rust 的优势子系统。然而,由于必须公开一个 C API,你将失去更丰富的类型系统的大部分优势,以及 Rust 作为一种语言为子系统的消费者带来的保证。类似地,约翰内斯·伯格询问是否用 Rust 替换子系统的部分,但将驱动程序留在 C 中——有效地与现有计划相反。 Ojeda 再次表示这是可能的,但告诫不要失去 Rust 功能和保证。此外,有些架构此时没有 Rustcompiler 可用,因此现在考虑基于 Rust 的子系统可能还为时过早。该项目的最终目标并不完全清楚。如果所有的 Linux 驱动程序都是用 Rust 编写的,那么仍然会有很多重要的部分运行不安全的 C;如果 Rust 同时证明自己,下一步是否会取代那些?但在这种情况下,“证明自己”的实际含义也不清楚。学习一门具有所有不同行为、怪癖和特质的新语言,对于已经在维护现有内核代码中处理相当多的复杂性的开发人员来说是一个非常大的要求。更不用说在已有功能的基础上提供新功能的额外复杂性。两个线程(和其他地方)的评论者似乎相当普遍的怀疑态度可能源于这种学习负担。将 Rust 添加到内核需要大量工作,对于很多不同的人来说,除了承诺之外,没有明确和明显的好处,只有用 Rust 编写的整个内核才能真正完全实现。 幸运的是,该项目可以提供一些明确的“胜利” “在早期,这清楚地展示了该语言的潜力和在像内核这样的大型复杂代码库上增量使用的能力。没有它,项目可能很难在“生锈”内核的目标上取得很大进展。
(登录发表评论)> Ojeda 试图在一定程度上减轻这些恐惧,但确实承认大多数内核开发人员最终需要了解该语言。我认为这是指这封电子邮件:https://lwn.net/ml/ksummit-discuss/CANiq72=LrxpE_2WmdDdb5...基本上它似乎是在说如果项目成功,最终结果应该是,如果你想要更改具有 Rust 绑定的 API,那么您需要了解这些 Rust 绑定,以便您也可以调整它们。这很有意义,如果您更改破坏其他内核代码的内核代码,那么您需要修复其他代码。但它确实清楚地说明了这个项目的范围以及承诺这个项目的决定有多大。为什么在 LWN 文章中如此偏向于谈论 Rust?我的意思是,这个补丁集可能(希望)永远不会合并,它的 lwn 文章与发布的版本一样多。在这里发现了多少重要的补丁集到 v2+(并且有机会登陆)?这是一个关于内核项目未来的基本决定;它似乎值得多看一看。借调。有史以来最重要的变化之一。请继续在更多报道而不是更少方面犯错。你不喜欢的偏见是我喜欢的偏见。有些人想要改善 Linux 环境。他们的努力可能会被拒绝,因为他们最终不符合合理的进入标准。这无关紧要。相关的是他们有一个与大多数其他改进工作完全不同的目标,并且相信它足以花费资源(时间、金钱等)。如果他们失败了,那么这种失败将影响未来的努力,包括 Rust 以外的语言。很好的答案,谢谢!最重要的是,Rust 可能会给 Linux 内核带来新的开发人员,否则他们不会接触它。 Rust 是一种适销对路的编程语言,C 是传统语言。从任何现代编程语言切换到 C 感觉就像进入了雷区。从用户空间到内核开发时更是如此。
> 从任何现代编程语言切换到 C 感觉就像进入了雷区。从用户空间到内核开发时更是如此。确切地。 C有很多footguns。 C++ 有更多的脚步声。你可以用 C++ 编写漂亮、干净、简洁的代码……但你无法避免它们——尤其是当编译器开发人员每年向语言中添加越来越多的代码时。 Rust 不会试图从语言中删除footguns(实际上,当您对内核进行编程时这基本上是不可能的),而是尝试确保它们中的每一个都被标记为不安全。这就是为什么从理论上讲,驱动程序听起来是最有希望移植到 Rust 的原因:它们通常是由硬件人员编写的,他们没有判断所有这些步枪的经验。并且,希望 API 可以提供给他们,允许他们在默认的、安全的、Rust 中编写驱动程序。但是如果你将内核的核心重写为 Rust ......收益会小得多:你越接近核心数据结构的黑暗魔法,就会有更多的内在脚步。这也是为什么为真正的硬件实现真正的驱动程序是一个重要的里程碑:某些讨厌的脚步声存在于内核中,因为它们是与硬件交互的固有特性,很高兴看到甚至移植一个驱动程序需要多大的胶水代码。如果胶水代码比实际的驱动程序代码多,这很好(它最终会与所有驱动程序共享,所以这没什么大不了的),但是如果驱动程序本身充满了许多不安全的块,那么整个练习就会变成有点无意义。 Rust 并没有试图彻底删除footguns 是不正确的。一些 C++ 步炮,尤其是继承自或建立在 C 兼容性基础上的 C++ 步炮(即使它们不一定是 C 中的步炮)是被遗忘的过去的遗物。例如,为什么允许“char” _either_ 有符号或无符号并且由实现来选择?因为当时的硬件千差万别。快进到 21 世纪,没有人希望他们在他们使用的 CPU 上拥有更快的 Rust i8 和 u8。那不是一回事。所以,如果你想要 i8 你就要求 i8,如果你想要 u8 你就要求 u8,因此你不能因为得到一种不是你想象的类型而大吃一惊。同样,为什么有符号整数在溢出时有未定义的行为?古代硬件以多种方式表示负数。因此,为了最大的兼容性,C 无法指定会发生什么。也许应该说平台定义,现在太晚了。很好,但是今天可用的每个平台都有二进制补码算法,所以这没有用。 Rust 可以预先说明这一点。调试造成恐慌!如果您溢出,如果错误一直存在到 Production ,您将获得二进制补码算法并祝您好运。如果您明确_想要_包装或饱和算术(后者在音频中尤其有意义),您可以在 Rust 中请求它们,无论是用于一个操作(例如饱和加法)还是作为变量的类型,您都会得到您想要的要求,没有恐慌,编译器为这种情况生成了良好的优化代码。但是,即使没有消除溢出的“未定义行为”的脚步。有时编译器变得更好,我们不再需要说明可以推断出什么。 C 和 C++ 都有一对一元增量运算符。他们是步兵。 b++ 和 ++b 都比 b += 1 的输入要少一些,但是它们将很多出错的机会挤进了这么少的字符中。为什么是其中两个?因为 C 和 C++ 使这些一元运算符具有结果以及它们的赋值效果,即使您很少需要它,并且结果是不同的。在 Rust 中,一元增量不存在,其他赋值运算符没有结果(严格来说,它们的结果是空元组),如果你的意思是 a = b; b+= 1 你可以这样写,如果你的意思是 b+= 1; a = b 你可以这样写。别担心,现代编译器很聪明,你不会因为输入稍微多一点而得到更慢的代码。有时同时明确问题范围。 Rust 缺少 C 和 C++ 的“volatile”关键字,我之前曾详细讨论过。该关键字可能是错误的。它在 Unix 中的用途在 Rust 中作为一组名为 volatile_read 和 volatile_write 的编译器内部函数存在(一组因为它们有不同的大小,事实证明你的硬件知道你是写了一个 32 位整数还是两个 16 位整数整数到内存),并且它在 C 或 C++ 中被错误地滥用的许多事情今天在这些语言中被明确禁止,因为它们无法工作。所以在实践中,Rust 的 volatile_read/ volatile_write 和 C++ volatile 关键字之间的差距只是你通过没有那种类型限定符而避免的。除了与 unsafe 关键字本身相关的事情(例如调用不安全的函数),我相信 Rust 只允许你在不安全的代码中做 3 件事,而你在安全代码中是不允许做的。您可以取消引用原始指针。您可以访问联合中的字段。你可以改变静态。就这样。这些确实是潜在的枪支(谁知道原始指针指向什么,如果有的话;也许联合中的那个字段不是其中包含有效数据的字段;嘿,另一个线程正在读取该静态变量,似乎在更改它)像一个坏主意)但这是一个比你预期的要小得多的集合。 > 也许应该说平台定义,现在太晚了。为什么为时已晚?正确的程序将保持正确,而错误的程序(他们的作者认为是正确的!)将变得真正正确。
事实上,这正是应该发生的事情:未定义的行为使实现者许可不捕获某些难以诊断的程序错误。它还确定了可能符合语言扩展的领域:实现者可以通过提供官方未定义行为的定义来扩充语言(强调我的)。签名溢出被声明为“未定义”没有......