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):kr crate 提供的 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()),
        })
    }
}

关键设计原则

  1. #[vtable] 宏:将 Rust trait 转换为 C 兼容的函数指针表(struct file_operations),这是 Rust 与内核 C 子系统之间的桥梁。
  2. Mutex vs Spinlock:直接使用 kernel::sync::Mutex,编译期检查睡眠上下文——不允许在持有 spinlock 时调用可能睡眠的 Rust 函数。
  3. 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.md rust-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%

踩坑记录

  1. Arc) 递归锁问题:早期实现中 Arc<Mutex<Node>> 的生命周期管理导致循环引用泄漏,需显式 Weak 打破。
  2. 与 C 的 binder_proc 互操作:Rust 端需要通过 ptr::addr_of_mut!(proc->rust_data) 读写 C 结构体字段,要求 #[repr(C)] 严格对齐。
  3. GPIO 与 IRQ 安全上下文:中断处理函数内禁止睡眠,但 Rust 的 kmalloc(GFP_KERNEL) 可能触发直接回收导致睡眠。应使用 GFP_ATOMIC 或 try_alloc!。

八、工程师视角:何时不该用 Rust

工程决策的前提是认清边界。以下场景仍建议用 C 或 eBPF:

  1. 极端内存紧张环境(< 4MB 可用):Rust 的 panic handler 和二进制元数据增加约 200KB 静默开销。
  2. 需要 asm! 深度内联汇编的场景:虽然 Rust 提供 asm! 宏,但内核 C 的 barrier()、clac()/stac() 等封装更成熟。
  3. 已有 500K+ 行成熟 C 代码:重写成本极高,建议对新功能用 Rust,老代码通过 FFI 渐进式迁移。
  4. 实时抢占路径(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 最新文档为准。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部