LWN:6.2内核中的Rust!

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

Rust in the 6.2 kernel

By Jonathan Corbet
November 17, 2022
DeepL assisted translation
https://lwn.net/Articles/914458/

6.1 版本的合并窗口带来了对用 Rust 编写内核代码的基本支持,这里的关键词是 "基本"。在 6.1 版本中可以创建一个 "hello world" module,但除此之外没有更多的可以做了。其实还有很多 Rust 代码在等待 review 以及合并到 mainline 上。Miguel Ojeda 现在已经发布了下一轮的 Rust patch,用来增加内核中的架构支持代码。

这 28 个 patch 专注于底层支持代码,它们仍然没有创建太多抽象来处理内核的其他部分。基于这些 patch,不会有新的亮眼的驱动程序。但是它确实是朝着给 Linux 内核的代码开发创造又一个可用环境所迈出的又一步。

作为 Rust 初始支持来举一个例子,由于内核有八个不同的日志级别(logging levels),从 "debug" 一直到 "emergency" 级别。每个级别都有一个宏定义用来让 print 更加方便。例如,如果即将发生 crash 了,就用 pr_emerg() 来大声地叫出来。6.1 中的 Rust 代码里定义了相应的宏,但只有两个:pr_info!()和 pr_emerg!();其他的日志级别的相关的宏都没有加。6.2 的首要任务似乎是填补其余的那些,也就是从 pr_debug!()这个极端到 pr_alert!()的另一个极端。还有 pr_cont!()用于处理由多次调用所拼凑起来的信息打印。这个内核模块的例子(https://lwn.net/ml/linux-kernel/20221110164152.26136-5-ojeda@kernel.org/)展示了所有的 print 宏的作用。

这组 patch 中所增加的一个比较复杂的宏是#[vtable]。内核大量使用了包括许多函数指针的结构;这些结构是内核对象模型的核心。一个典型的例子就是 struct file_operations,其中包含了对一个已经打开的文件可以做的各种动作的实现函数。从相对容易理解的操作如 read()和 write(),一直到更难理解的功能如 setlease()或 remap_file_range()。内核中任何可以表示为已经打开了的文件的对象都会提供这样的一个结构来实现对该文件的操作。

因此,像 file_operations 这样的操作结构看起来很像 Rust traits,而且它们确实可以在 Rust 代码中用 traits 的方式来实现。但是内核允许一个这样的结构中都不用定义那些用不到的函数;例如,remap_file_range()操作在大多数设备驱动中都是没有意义的。在内核的 C 代码中,这些缺失的操作就用一个空指针来表示;调用这些操作的代码都会检测到有空指针,从而转为执行一个默认的操作。然而,空指针是 Rust 世界要避免的东西,所以在 Rust 需要做一些额外工作才能真正表达 operation structure。

#[vtable]宏就是用来完成 C operation structure 和 Rust traits 之间必须进行的适配的。在 trait 声明和实现的时候都将使用这个宏,所以 trait 的定义就类似下面这样:

#[vtable]
pub trait Operations {
    /// Corresponds to the `open` function pointer in `struct file_operations`.
  fn open(context: &Self::OpenData, file: &File) -> Result<Self::Data>;
// ...
}

而一个具体设备的实现就类似下面这样:

#[vtable]
  impl kernel::file::Operations for some_driver {
      fn open(_data: &(), _file: &File) -> Result {
          Ok(())
      }
// ...
  }

如果要把这个实现递给内核的其他部分,那么就必须转化为合适的 C structure。Rust 可以创建这个结构,但是它很难检测出哪些操作是已经实现了的,以及哪些操作应该用一个空指针来表示。#[vtable] 宏有助于为每个定义了函数都生成一个专门的 constant member;在上面的例子中,some_driver 类型就会有一个常量 HAS_OPEN 成员,被设置为 true。生成 C operation structure 的代码可以查询这些常量(在编译时),并为 missing operation 插入空指针;可以从相关 patch 中了解到这里的工作细节。

为 6.2 提交的 patch 中增加了#[vtable],但并没有包含任何使用了它的地方。对此感兴趣的人可以通过查看 8 月份发布的这个巨大的 patch 来了解如何使用它;搜索#[vtable]和 HAS_就能找到使用这一基础设施的地方。

另一个新增的宏是 declare_err!(),它可以用来声明各种错误代码常量,如 EPERM。6.2 版本的内核可能会包括一整套用这个宏来声明的错误代码,而不是 6.1 版本中所实现的一个最小集合。还有一种机制可以将许多 Rust 内部的错误翻译成 Linux 的错误代码。

Rust Vec 类型实现了一个数组,它可以根据需要增长,从而放入更多内容。当然,增加 size 的时候就涉及到内存分配,这在内核中可能会失败。在 6.2 中,在内核中实现的 Vec 可能会有两个 method,分别叫做 try_with_capacity()和 try_with_capacity_in()。它们的作用与标准的 with_capacity()和 with_capacity_in() Vec method 类似,会预先分配内存从而方便后续存放数据,但有所不同的是,它们可以返回一个失败代码。try_ 变体的 method 会允许内核代码尝试分配所需大小的 Vec,并在分配失败时正确处理,而不是像标准版本的函数那样直接调用 panic()。

对于像编者这样的新手来说,Rust 的一个更令人困惑的方面是存在两种 string 类型:str 和 String;前者表示对存储在其他地方的字符串的一个 borrowed reference,而后者实际上拥有该字符串。内核的 Rust 支持将相应地定义两个变体,称为 CStr 和 CString,它们的功能与 C 语言的 string 相同。具体来说,它们处理的是一个表示为字节数组的字符串,并以 NUL 字符结束。将字符串传递给内核其他部分的 Rust 代码都会需要使用这些类型。

这组 patch 中最后实现了一些随机的组件,它们对未来新增更多 Rust 功能会是必要的。dbg!()宏使某些类型的调试更容易。有一些代码是支持 compile-time assertion 以及强制 build error 的。Either 类型可以容纳一个可以是两种不同类型中的一种的一个对象。最后,Opaque 类型是用于内核使用、但是 Rust 代码从不访问的那些结构。使用这种类型可以提高性能,因为在调用初始化函数之前,不需要对存放它的内存初始化为零。

可以看出,这些 patch 正在慢慢地构建起内核内的 Rust 代码,以备于今后可以在 Rust 中实现一些真正有用的功能,但这个过程还没有完成。目前还不清楚是否会有更多的 Rust 代码被推荐到 6.2 中,还是说这已经是 6.2 中计划合入的全部内容了。对于那些想开始用 Rust 做真正工作的开发者来说,这种进展速度可能显得很慢,但它确实有一个优势,那就是可以递进式地让内核社区理解以及 review。Rust-for-Linux 的工作已经进行了几年;要达到完整的功能可能还需要一段时间。

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

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

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

format,png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值