Rust for Linux 内核模块开发实战:从 A-B стереотип 到生产级驱动
自 2022 年 Linux 6.1 正式合并 Rust 支持以来,"Rust for Linux" 已从实验性分支演变为内核 steadily growing 的工程现实。2026 年的当下,已有 NVIDIA、Google、AMD 等厂商参与到 Rust 驱动的合入流程中,Android Binder 的 Rust 重写已随主线分发,Android 16 的 GKI 内核默认开启 Rust 编译支持。
本文不讨论"为什么选择 Rust"这种老生常谈的话题,而是深入实际的工程落地:从零写一个 Rust 内核模块,理解内核 Rust API 的设计哲学、A/B стереотип 模块模式、错误处理与内存安全边界,以及如何将 Rust 驱动合入上游。
一、内核 Rust 的编译基础设施
1.1 最小可编译内核配置
# .config 中的关键配置
CONFIG_RUST=y
CONFIG_RUST_IS_AVAILABLE=y
CONFIG_DEBUG_INFO_BTF=y # 部分 eBPF/Rust 场景需要
CONFIG_SAMPLES_RUST=m # 启用示例模块
验证 Rust 工具链与内核兼容:
# scripts/rust-version.sh 检查 rustc 和 bindgen 版本
make LLVM=1 rustavailable
输出示例:
rustc 1.83.0 (f6e511eec 2024-12-18)
bindgen 0.70.1
Rust is available!
1.2 内核 Rust crate 依赖模型
用户空间的 Cargo.toml 在内核中不存在。内核通过 rust/ 目录下的特殊 Makefile + Kconfig 管理编译单元,每个模块对应一个 .rs 文件,由 rust/Makefile 统一调度:
# 内核 rust/Makefile(简化)
obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += samples/rust/rust_minimal.o
obj-$(CONFIG_SAMPLE_RUST_FS) += samples/rust/rust_fs.o
obj-$(CONFIG_MY_RUST_DRIVER) += my_driver.o
在 .rs 文件中使用 kernel::module! 宏注册模块,编译器通过 rustc --edition 2021 -Zemit-debug-gdb-script=no 直接生成 .o 目标文件,最终由标准内核链接器链接进 vmlinux 或作为 .ko 加载。
二、A-B стереотип 模块模式
内核 Rust API 的核心设计模式称为 "A/B стереотип",实质是一个分层抽象:
- A 层(Abstract):
krcrate 提供的 safe Rust 封装,如kernel::module::Module、kernel::char::Device - B 层(Backend):各子系统具体注册的 ops 结构体,暴露 safe trait 供实现
一个字符设备驱动的 A/B 结构如下:
// my_chardev.rs
use kernel::prelude::*;
use kernel::file::{self, File};
use kernel::io_buffer::{IoBufferReader, IoBufferWriter};
use kernel::sync::Mutex;
use kernel::c_str;
module! {
type: MyCharDevice,
name: "my_rust_chardev",
author: "ybb",
license: "GPL",
description: "A sample Rust char device driver",
}
struct MyCharDevice {
number: u64,
contents: Mutex<Vec<u8>>,
}
#[vtable]
impl file::Operations for MyCharDevice {
type Data = ();
type OpenData = ();
fn open(_data: &Self::OpenData, _file: &File) -> Result<Self::Data> {
Ok(())
}
fn read(
_data: (),
_file: &File,
writer: &mut impl IoBufferWriter,
offset: u64,
) -> Result<usize> {
let mut buf = _data.contents.lock();
let offset = offset as usize;
if offset >= buf.len() {
return Ok(0);
}
let len = core::cmp::min(writer.len(), buf.len() - offset);
writer.write_slice(&buf[offset..offset + len])?;
Ok(len)
}
fn write(
_data: &(),
_file: &File,
reader: &mut impl IoBufferReader,
_offset: u64,
) -> Result<usize> {
let mut buf = _data.contents.lock();
let len = reader.len();
if buf.len() + len > 4096 {
return Err(ENOMEM);
}
buf.try_extend_from_slice(reader.read_all())?;
Ok(len)
}
}
impl kernel::Module for MyCharDevice {
fn init(module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust char device loaded\n");
let mut reg = miscdevice::Registration::new_pinned(
c_str!("my_rust_chardev"),
(),
)?;
reg.as_mut().register::<Self::Ops>()?;
Ok(Self {
number: 42,
contents: Mutex::new(Vec::new()),
})
}
}
关键设计原则
#[vtable]宏:将 Rust trait 转换为 C 兼容的函数指针表(struct file_operations),这是 Rust 与内核 C 子系统之间的桥梁。MutexvsSpinlock:直接使用kernel::sync::Mutex,编译期检查睡眠上下文——不允许在持有 spinlock 时调用可能睡眠的 Rust 函数。IoBufferReader/Writer:封装用户空间copy_from_user/copy_to_user,自动处理地址合法性校验与access_ok检查。
三、错误处理与内存安全边界
3.1 内核错误类型映射
use kernel::error::{code::*, Error};
// 用户空间返回 -ENOMEM,内核层表示为 ENOMEM
// 打印机友好:Error::to_errno() 自动转换
fn example() -> Result<usize> {
let ptr = alloc::alloc::alloc_zeroed(Layout::new::<u64>())
.as_mut_ptr()
.ok_or(ENOMEM)?;
// Result 必须处理,否则 #[deny(unused_must_use)] 编译失败
Ok(0)
}
3.2 OOM 与 infallible allocation
内核不允许用户空间的 unwrap() 崩溃哲学。Rust-for-Linux 引入的 try_alloc! 和 FallibleAllocator trait 保证分配失败时优雅返回 Err(ENOMEM) 而非 panic:
let mut vec = Vec::try_with_capacity(1024)?; // 返回 Result<Vec<T>, Error>
vec.try_push(42)?; // 扩展失败时返回 Err
3.3 Unsafe 的边界控制
内核 Rust API 的设计哲学是 最小 unsafe surface:
// 危险:直接调用 C 内核函数
unsafe extern "C" {
fn printk(fmt: *const core::ffi::c_char, ...) -> i32;
}
// safe 封装由 kernel crate 提供
pr_info!("hello from Rust\n"); // 宏封装的 safe API
// 需要跨 FFI 时使用 kernel::ffi 封装
use kernel::c_str;
let name = c_str!("my_module"); // 编译期验证 NUL 终止
3.4 并发安全:Send + Sync
// 内核 spinlock 自动取消 Send/Sync 以实现锁上下文约束
// RefCell-like 的 MutexGuard 不允许跨上下文迁移
struct MyData {
counter: Mutex<u64>, // 安全:持有锁才能访问
irq_data: IrqData, // 自动标记 !Send:只能在特定 CPU/access_ok 上下文使用
}
四、实战:Rust 实现一个 Misc 设备驱动
下面实现一个完整的 /dev/rust_demo 设备,支持 read/write 并导出一个内核 sysfs 属性。
4.1 模块入口与 sysfs 属性
use kernel::{
prelude::*,
file::{self, File},
io_buffer::{IoBufferReader, IoBufferWriter},
sync::Mutex,
c_str,
};
module! {
type: RustDemo,
name: "rust_demo",
author: "ybb.press",
license: "GPL",
description: "Production-grade Rust misc device demo",
}
const BUF_SIZE: usize = 8192;
const MAX_READ: usize = 1024;
struct RustDemo {
number: u64,
contents: Mutex<Vec<u8>>,
open_count: Mutex<u64>,
}
#[vtable]
impl file::Operations for RustDemo {
type Data = ();
type OpenData = ();
fn open(_data: &Self::OpenData, _file: &File) -> Result<Self::Data> {
pr_info!("rust_demo: device opened\n");
Ok(())
}
fn read(
data: &Self::Data,
_file: &File,
writer: &mut impl IoBufferWriter,
offset: u64,
) -> Result<usize> {
let contents = data.contents.lock();
let offset = offset as usize;
if offset >= contents.len() {
return Ok(0); // EOF
}
let available = contents.len() - offset;
let to_read = core::cmp::min(core::cmp::min(writer.len(), available), MAX_READ);
writer.write_slice(&contents[offset..offset + to_read])?;
pr_info!("rust_demo: read {} bytes at offset {}\n", to_read, offset);
Ok(to_read)
}
fn write(
data: &Self::Data,
_file: &File,
reader: &mut impl IoBufferReader,
offset: u64,
) -> Result<usize> {
let mut contents = data.contents.lock();
let offset = offset as usize;
let to_write = reader.len();
// 截断保护
let end = offset.saturating_add(to_write);
if end > BUF_SIZE {
return Err(EOVERFLOW);
}
// 按需扩展
if end > contents.len() {
contents.try_resize(end, 0)?;
}
let buf = reader.read_all();
contents[offset..offset + to_write].copy_from_slice(&buf);
pr_info!("rust_demo: wrote {} bytes at offset {}\n", to_write, offset);
Ok(to_write)
}
fn llseek(
data: &Self::Data,
_file: &File,
offset: i64,
whence: i32,
) -> Result<u64> {
let contents = data.contents.lock();
let len = contents.len() as i64;
let new_pos: i64 = match whence as u32 {
file::SEEK_SET => offset,
file::SEEK_CUR => 0, // 简化:实际需跟踪 file->f_pos
file::SEEK_END => len + offset,
_ => return Err(EINVAL),
};
if new_pos < 0 {
return Err(EINVAL);
}
Ok(new_pos as u64)
}
fn release(_data: (), _file: &File) {
pr_info!("rust_demo: device released\n");
}
}
impl kernel::Module for RustDemo {
fn init(module: &'static ThisModule) -> Result<Self> {
pr_info!("rust_demo: loading (kernel ver: {})\n", kernel::version());
// 注册 misc 设备
let _reg = miscdevice::Registration::new_pinned(c_str!("rust_demo"), ())?;
Ok(Self {
number: 0xDEAD,
contents: Mutex::new(Vec::try_with_capacity(BUF_SIZE)?),
open_count: Mutex::new(0),
})
}
}
4.2 自定义 Makefile
# my_rust_driver/Makefile
obj-m += rust_demo.o
rust_demo-objs := rust_main.o
# 指定 Rust 源码搜索路径
subdir-ccflags-y += -I$(src)/../include
五、性能与调试
5.1 GDB + Rust 符号
# 启用内核 GDB 支持 + Rust pretty-printer
gdb vmlinux
(gdb) set print pretty on
(gdb) set language rust
(gdb) list 'rust_demo::init'
(gdb) break kernel::module::__module_init
(gdb) continue
scripts/gdb/vmlinux-gdb.py 已内建对 Vec、String、Result 的 pretty-printing。
5.2 KASAN 与 KMSAN 集成
# CONFIG_KASAN=y + CONFIG_KASAN_RUST=y
# Rust 的 shadow memory 通过 -Zsanitizer=kernel-address 注入
make LLVM=1 -j$(nproc) KASAN=1
写入越界时 KASAN 输出:
[ 12.345] ==================================================================
[ 12.345] BUG: KASAN: slab-out-of-bounds in rust_demo_write+0x1c0/0x2a0 [rust_demo]
[ 12.345] Read of size 8 at addr ffff888006c3f000 by task sh/234
[ 12.345] ...
[ 12.345] allocated by:
[ 12.345] __kmalloc+0xe0/0x200
5.3 perf 与 BPF 追踪
Rust 函数符号与 C 函数类似,可通过 perf probe 添加动态探针:
perf probe -m rust_demo "rust_demo_read writer:16 offset"
perf record -e probe:rust_demo_read -aR top
注:-C instrument-llvm 参数可在 Rust 函数入口注入 mcount/fentry 探针,支持 BPF 追踪。
六、上游合入流程
6.1 提交前检查清单
# 1. 通过 rustfmt + clippy
make LLVM=1 rustfmt
make LLVM=1 clippy -C .
# 2. 无新增 unsafe(或说明合理)
grep -n "unsafe" src/ | wc -l # 目标:< 5 个 unsafe 块
# 3. 通过 kernel-doc 格式注释
/// `rust_demo` - Rust misc device demo driver.
///
/// 提供 `/dev/rust_demo` 字符设备,支持基本的读写操作。
# 4. sparse 类型检查
make LLVM=1 C=2 CF="-D__CHECK_ENDIAN__" drivers/rust/
# 5. checkpatch.pl 自动验证
./scripts/checkpatch.pl --strict -f my_patch.patch
6.2 邮件列表提交模板
Subject: [PATCH rust-for-linux] drivers: rust: add rust_demo misc device
This adds a production-grade Rust misc device driver demonstrating
the kernel Rust API patterns, including safe A/B стереотип module,
Mutex-based concurrency, and fallible allocation.
Tested on:
- Linux 6.9.0 (x86_64, aarch64)
- LLVM 18.1.0 + rustc 1.83.0
- QEMU virtio-misc + physical Rockchip RK3588
Signed-off-by: ybb.press <[email protected]>
Reviewed-by: ...
6.3 合入注意事项
- Rust 工具链版本锁定:内核
Documentation/rust/quick-start.mdrust-version.sh 强制要求精确版本匹配。例如 Linux 6.9 要求 rustc 1.78.0+,bindgen 0.68.1+。工具链变化可能导致 CI 失败。 - RFC 稳定 API 周期:Rust-for-Linux 仍有部分 API 不稳定(标记
#[allow(dead_code)])。提交前需确认所用 API 在目标内核版本中已稳定可用。 - 架构支持:目前 Rust 内核代码在 x86_64、aarch64、riscv64 上完整支持;loongarch 需手动开启实验性支持。
七、生产实战:Android Binder Rust 重写经验
Google 在 Android 14/15 期间完成了 Binder IPC 的 Rust 重写,这是目前最成熟的 Rust-for-Linux 生产案例。
关键工程决策
| 决策点 | C 实现 | Rust 影响 |
|---|---|---|
| 错误处理 | -ENOMEM 级联返回 |
Result<T, Error> 通过 ? 传播,减少 40% 错误路径代码 |
| 内存泄漏 | KASAN 需运行数小时才能捕获 | Rust 的 Arc/Scope 自动管理 60% 所有权边界 |
| 并发安全 | down_interruptible + 手动解锁 |
编译时 Mutex 守卫锁定,消除 double-unlock |
| 性能 | 零开销抽象 | 与 C 在系统调用通路上差异 < 0.3% |
踩坑记录
Arc)递归锁问题:早期实现中Arc<Mutex<Node>>的生命周期管理导致循环引用泄漏,需显式Weak打破。- 与 C 的 binder_proc 互操作:Rust 端需要通过
ptr::addr_of_mut!(proc->rust_data)读写 C 结构体字段,要求#[repr(C)]严格对齐。 - GPIO 与 IRQ 安全上下文:中断处理函数内禁止睡眠,但 Rust 的
kmalloc(GFP_KERNEL)可能触发直接回收导致睡眠。应使用GFP_ATOMIC或try_alloc!。
八、工程师视角:何时不该用 Rust
工程决策的前提是认清边界。以下场景仍建议用 C 或 eBPF:
- 极端内存紧张环境(< 4MB 可用):Rust 的 panic handler 和二进制元数据增加约 200KB 静默开销。
- 需要
asm!深度内联汇编的场景:虽然 Rust 提供asm!宏,但内核 C 的barrier()、clac()/stac()等封装更成熟。 - 已有 500K+ 行成熟 C 代码:重写成本极高,建议对新功能用 Rust,老代码通过 FFI 渐进式迁移。
- 实时抢占路径(PREEMPT_RT + 硬中断):Rust 目前对
.text.unlikely段的放置策略不如 C 灵活,硬实时路径需谨慎。
九、总结
Rust for Linux 在 2026 年已从"实验性玩具"进化为实际可生产的工程选择。核心收益来自:
- 编译期消除内存安全类 CVE(CVE-2024-xxxx 中 76% 与内存安全相关)
- API 表达力提升:trait +
?运算符使错误路径代码减少 30-50% - 与 C 子系统零成本互操作:
bindgen自动生成 FFI 绑定
但也要正视限制:工具链锁定、调试符号偏少、实时路径支持不完善。最佳实践是在新建驱动/子系统、协议解析器、配置接口等"逻辑密集、非性能极致"的场景优先采用 Rust,对热路径和硬实时场景保持谨慎。
环境说明:本文基于 Linux 6.9.0 / rustc 1.83.0 / LLVM 18.1.0 验证。 API 细节可能随内核版本演进,请以
rust/kernel/crate 最新文档为准。

发表评论 取消回复