LWN: Rust 中对于Linux kernel有用的关键特性!

Linux News搬运工

共 3917字,需浏览 8分钟

 · 2021-11-29

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

Key Rust concepts for the kernel

By Jonathan Corbet
September 17, 2021
Kangrejos
DeepL assisted translation
https://lwn.net/Articles/869317/

Kangrejos online 会议的第一天主要是介绍为了将 Rust 编程语言引入 Linux 内核而做的那些工作。第二天,会议组织者 Miguel Ojeda 转向了介绍 Rust 语言本身,强调 Rust 可以为内核开发而提供什么。会议最终为那些对这个项目感到好奇,但还没有时间熟悉 Rust 的人提供了一些有用的信息。

Ojeda 一开始就强调,这个讲座并不算是教程。要真正学习这门语言的话,应该去找一本好书,然后书中开始学习。在一个小时的演讲中不可能涵盖所有必需内容(随着时间的推移,也证明了确实如此),但他希望能够展示 Rust 背后的一些关键想法。但最终,真正理解这种语言的唯一方法仍是坐下来写一些代码。

Reading material

对于想要学习 Rust 的开发者,有很多资源可以利用,有一些书籍可用。最权威的书似乎是《Rust Programming Language》,可以在网上免费获得。他认为这本书对 Rust 进行了很好的介绍,同时也可以作为 Rust 项目中的文档风格的一个展示。对内核开发特别感兴趣的开发者不需要读完这本书。比如其中关于并发(concurrency)的许多讨论并不适用于内核环境。

他说,《Programming Rust》也相当不错。它是针对有经验的程序员的,因此可能对内核开发者有用。不过,这本书是他给出的书单上唯一不是免费的资源。对于那些想直接进来尝试的读者,《Rust by Example》给出了一系列的练习,每一个练习里都介绍了一个新的语言特性。

然后,还有《The Rustonomicon》。这本书并不完整,它着重关注了编写 unsafe Rust 会面临的挑战。如果 Rust-for-Linux 项目的目标最终能够完全实现,那么驱动程序开发者就不会需要编写 unsafe Rust,这样的话可能就不需要读这本书了。此书中有很多具体细节以及底层信息,但它也证明了一个关键观点:大多数 Rust 开发者都不应该涉及到这么底层的 Rust 应用。

最后,《The Rust Reference》这本书也不是一个完整的手册,而是一本为那些 "喜欢阅读语言标准(language standard)的人" 所准备的非常棒的书籍。大多数 Rust 开发者都不会需要读这本书。Ojeda 在这些书籍介绍之后也进行了总结,他建议大多数开发者使用《The Rust Programming Language》或《Programming Rust》就好。

当然,Rust 项目也提供了很多其他形式的文档。Ojeda 说,对于 C 语言来说,C 语言的标准就是其文档,但 Rust 则不同。到处都是文档,其中包含了非常多的解释以及例子,而且经常是直接从代码中生成的。在 Linux kernel 中添加 Rust 的这个工作中产生的文档也采用了类似的方法,不过是用 rustdoc 来编写的,而不是使用内核里的 Sphinx 文档系统。如何整合这两套文档,目前仍然没有确定。

Tools

Ojeda 接着介绍了一些工具,在查看 Rust 代码以及它编译生成的机器代码时很有用。其中之一是 Rust playground,这个工具可以用来编译代码、运行 linter 检查、查看汇编代码等等。

他本人更喜欢使用 Compiler Explorer。这个工具同样提供了上述那些功能,但不仅仅限于 Rust。例如,它可以用来比较不同语言所产生的汇编代码,针对这个场合还提供了一个漂亮的框图显示界面。Compiler Explorer 也可以执行相关代码并显示结果。

The Language

虽然有些人把 Rust 描述为 "a safer C",但 Ojeda 并不喜欢这个说法。这更像是一个具有更安全的类型系统(safer type system)的 C 语言新版本,这跟 safety-critical (把安全作为重心)是不一样的。他说,Rust 也可以被描述为一个 "cleaned-up C"。他展示了三种语言中类似的代码片段来说明这一点:

map(v, [](const auto & x) { return x.get(); });   // C++
map(v, lambda x: x.get())    # Python
map(v, |x| x.get());  // Rust

他说,Rust 版本更干净、更容易。C++ 可能包含了很多有用的功能,但开发者往往害怕使用那些功能。Rust 让这一切变得更容易了。

他花了一些时间来讨论 Rust 中的 "safety" 这个概念,这部分与第一天的讨论有不少重复的内容。大家所说的 Rust 是 "safe" 的,在于它没有那些 undefined behavior,也就是未定义的行为;也不存在那些 "编译器可以随意做各种疯狂的动作" 的情况。许多人们不希望出现的情况下的行为,包括 kernel panic、内存泄漏和整数的溢出等等,都被很好地定义了出来,从这一点来说 Rust 是 safe 的(不过编译器还是可以检查溢出的)。当然,逻辑错误也是 "safe" 的,语言并不能保证程序会按照开发者的意图运行。

但是许多其他类型的问题都可以通过 safe Rust 代码来避免。比如下面 Ojeda 展示了一个简单的 C 函数来说明一类 undefined behavior:

int f(int a, int b) {
return a/b;
}

这个函数可能会出现两种方式的未定义行为。最明显的就是调用者给 b 传递了 0 这种情况,此时编译器可以按自己的喜好来产生任何不确定的处理方式。另一种未定义的情况是,如果 a 是 INT_MIN,b 是-1。在用二进制补码表示法中,最大的负整数在正数范围内没有对应的数字,所以除法的结果就不能被表示了。同样,当这种情况发生时编译器就可以按自己的喜好来实现后续处理了。

如果我们用 Rust 来写一个类似的函数:

pub fn f(a: i32, b: i32) -> i32 {
a/b
}

Ojeda 使用 Compiler Explorer 来展示了一下这个函数的编译结果,其中已经带有了包含了对上述两种未定义行为情况的检测。如果遇到这两种情况,程序就会中止。这可能不是程序员想要的结果,但继续运行从而产生未定义行为也并不是一个好结果。

此时大家对所有这些检查对性能带来的开销进行了一些简要讨论。这肯定是有开销的,但似乎没有人测量过在性能非常敏感的情况下的影响到底有多大。Ojeda 指出,最坏的情况下,还是可以把代码放到 unsafe 的代码部分中,这样就可以去除所有这些检查。在这次会议上,"unsafe" 经常被用来作为一个 "逃生舱门把手,escape hatch",每当语言的限制变得过于难以接受时,这种方法 kqyi 去除掉许多限制。

也还有各种技巧可以用来向编译器表明某些情况是不可能出现的,这时它将自动省略掉这部分检查。例如,如果一个人定义了一个有界整数类型(bounded-integer type),编译器就知道它的值是将在其接受范围内,那可能就不需要执行溢出检查了。这种技巧通常会需要把相应的必要检查放到构造函数中,在那里只需要做一次检查就好。

Ojeda 还提供了一个例子,是一个函数中的 C 代码,看起来像这样:

int x;  /* not initialized */

while (f())
x = 42;
return x;

通过查看这个函数的汇编输出,可以看到编译后的代码只是返回一个常数 42。赋值给 x 的循环体可能从未执行过,但在这种情况下引用 x 的话就会使程序产生未定义行为。编译器被允许假设这种情况永远不会发生。在 C 语言的这种世界观下,肯定会被赋值为 42。相反,Rust 会在这种情况下抛出一个编译时错误,并强制对该变量进行显式的初始化。

Laurent Pinchart 指出,无论使用哪种语言,开发人员都必须训练他们自己的大脑来避免在开发中产生未定义行为。这里的区别是,Rust 编译器会抓住错误,而 C 编译器往往会什么都不说。他说,在用 Rust 这样的语言训练你的大脑后,通常也可以帮助你写出更好的 C 语言代码。

会议的时间到此为止,但 Ojeda 又用一系列幻灯片介绍了与内核开发者相关的其他 Rust 语言特性。其中包括不允许访问错误成员的 union 类型,动态分配对象的隐式释放功能,处理有符号的整数溢出,避免数据竞态的机制,等等。简而言之,Rust 中为系统开发者提供了很多东西,远远超出一个简短的会议所能涵盖的内容。

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

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

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



浏览 36
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

举报