如何使用 rust 写内核模块
作者:卜比
近年来,Rust 语言以内存安全、高可靠性、零抽象等能力获得大量开发者关注,而这些特性恰好是内核编程中所需要的,所以我们看下如何用 rust 来写 Linux 内核模块。
Rust 与内核模块
虽然 Rust 支持已经在 LinuxKernel6.1 版本合并到主线了,所以理论上来说,开发者可以使用 Rust 来为 Linux6.1 写内核模块。
但实际开发工作中,内核版本不是最新的,比如 Debian 11 的内核就是 5.10 版本的,那么在这种情况下,该如何用 Rust 写内核模块呢?
原理
Rust 如何向内核注册回调、如何调用内核代码。Rust 和 C 的互操作性
Rust 如何编译到目标平台上。Rust 的 target 配置
Rust 如何申明内核模块入口、并添加特殊 section。Rust 内核模块的二进制约定
Rust 和 C 的互操作性
第一个问题基本上就是 C 和 Rust 的互操作性了。
得益于 Rust 的抽象层次,C 语言和 Rust 的互相调用都是比较容易的。rust 官方也提供了 bindgen 这样,根据 .h 文件生成 .rs 文件的库。
这样一来,貌似直接使用 bindgen 将内核头文件翻译成 .rs 就可以了?
但还有一个问题,如何获取内核头文件路径呢?
可以使用一个 dummy 内核模块,在编译过程中把编译参数导出来,其中包含了头文件路径,编译参数等,用于 bindgen 生成代码。
Rust 和 target 配置
内核模块和普通的程序相比,主要的不同在于:
内核模块是 freestanding 的,没有 libc、内存分配也比较原始
内核模块对于异常处理等有特殊约定
Rust 提供了 no_std 机制,可以让 rust 代码编译成 freestanding 二进制;Rust 也提供了自定义 target 的方式,可以自定义声明生成二进制的规范。
内核模块的二进制约定
内核对内核模块有一些约定:
通过 .modinfo 等 section 来声明模块信息
提供 init_module、cleanup_module 来提供内核模块的安装和卸载功能
在这一块,Rust 提供了 link_section 来自定义 section,也支持 extern "C"来导出函数。
此外,这些底层的操作,可以由内核提供一些 C 语言宏来简化代码,Rust 也提供了宏,可以用来做类似的事情。
一个小例子
说了这么多,我们来看一个带注释的例子:
具体的构建和运行:
已在内核 5.10.0-17-amd64 上测试。
具体的代码以及相关配置,可以参考 GitHub 仓库:https://github.com/robberphex/linux-kernel-module-rust
一些小细节
VSCode 支持
由于 rust-analyzer 对于自定义 target,多模块的支持不够,所以我们暂时需要手动配置下 settings.json 才能正常开发:
其他高级功能
比如字符设备、sysctl 等功能,可以参考项目中相关的测试代码。
更多规划
和 Rust-for-Linux 保持 API 一致。(Rust-for-Linux 例子:https://github.com/Rust-for-Linux/linux/blob/d9b2e84c0700782f26c9558a3eaacbe1f78c01e8/samples/rust/rust_chrdev.rs)
Rust 提供的内存安全性、零抽象等能力,恰好是内核领域亟需的特性和能力。比如内核态如果出现内存泄漏、野指针,一会造成很大影响、二来也很难调试。在这个领域,我们可以借助 Rust 的能力来构造更加安全、更大的项目。
原始项目是 fishinabarrel/linux-kernel-module-rust,但目前提示使用 rust-for-linux,已经 archived。然而,考虑到目前旧版本内核还有很多,所以我重新修复了这个项目的一些环境,让大家在旧版本内核上能够用 Rust 编写内核模块。
版权声明: 本文为 InfoQ 作者【阿里巴巴中间件】的原创文章。
原文链接:【http://xie.infoq.cn/article/ce85483552f73a5b9192467fd】。文章转载请联系作者。
评论