Rust与内核模块
虽然Rust支持已经在Linux Kernel 6.1版本合并到主线了,所以理论上来说,开发者可以使用Rust来为Linux 6.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也提供了宏,可以用来做类似的事情。
一个小例子
说了这么多,我们来看一个带注释的例子:
#![no_std] // no_std用于表示没有std库,即freestanding环境 extern crate alloc; use alloc::borrow::ToOwned; use alloc::string::String; // 我们以printk为底层,提供了println use linux_kernel_module::println; // 这个struct代表内核模块 struct HelloWorldModule { message: String, } // 实现内核模块初始化方法 impl linux_kernel_module::KernelModule for HelloWorldModule { fn init() -> linux_kernel_module::KernelResult<Self> { println!("Hello kernel module from rust!"); Ok(HelloWorldModule { message: "on the heap!".to_owned(), }) } } // 提供内核模块卸载方法 impl Drop for HelloWorldModule { fn drop(&mut self) { println!("My message is {}", self.message); println!("Goodbye kernel module from rust!"); } } // 通过kernel_module宏,export了内核模块的相关信息 linux_kernel_module::kernel_module!( HelloWorldModule, author: b"Fish in a Barrel Contributors", description: b"An extremely simple kernel module", license: b"GPL" );
具体的构建和运行:
$ cd linux-kernel-module-rust/hello-world $ RUST_TARGET_PATH=$(pwd)/.. cargo +nightly xbuild --target x86_64-linux-kernel-module $ make $ insmod helloworld.ko $ rmmod helloworld $ dmesg | tail -n 3 [521088.916091] Hello kernel module from rust! [521174.204889] My message is on the heap! [521174.204891] Goodbye kernel module from rust!
已在内核5.10.0-17-amd64上测试。
具体的代码以及相关配置,可以参考GitHub仓库:
https://github.com/robberphex/linux-kernel-module-rust
一些小细节
- VSCode支持
由于rust-analyzer对于自定义target,多模块的支持不够,所以我们暂时需要手动配置下settings.json才能正常开发:
{ "rust-analyzer.cargo.extraEnv": { "RUST_TARGET_PATH": "/root/linux-kernel-module-rust" }, "rust-analyzer.cargo.target": "x86_64-linux-kernel-module", "rust-analyzer.server.extraEnv": { "RA_LOG": "lsp_server=debug", "RUST_TARGET_PATH": "/root/linux-kernel-module-rust" }, "rust-analyzer.trace.server": "verbose", "rust-analyzer.linkedProjects": [ "hello-world/Cargo.toml", "Cargo.toml" ], }
- 其他高级功能
比如字符设备、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编写内核模块。