Rust 进入 Linux 内核:从恐惧到拥抱——内存安全系统编程的范式革命

2022 年 9 月,Linus Torvalds 将 RUST 支持合并入 Linux 6.1 内核主线。这一里程碑事件标志着 Linux —— 这个用 C 语言构建了三十年的操作系统内核 —— 正式向第二种编程语言敞开大门。三年过去了,Rust-for-Linux 项目已经从"能否编译通过"走向了生产级驱动代码。本文深入解析 Rust 在内核中的 ABI 绑定、与 C 的安全互操作模型、真实驱动实践,以及这场变革对系统编程未来的深远影响。

一、为什么内核需要 Rust?事故的代价

1.1 内核安全漏洞的统计真相

CVE 数据库显示,Linux 内核约 65-70% 的高危安全漏洞源于内存安全问题(缓冲区溢出、释放后使用、空指针解引用、越界访问)。C 语言的手动内存管理和裸指针语义为这些漏洞提供了天然土壤。Microsoft 的研究表明,其产品中约 70% 的漏洞同样是内存安全问题,Apple 的数据也与此接近。

到 2025 年,Rust 在三个方面已证明其对内核开发的价值:

  • 类型系统驱动的内存安全:编译期消除 use-after-free、data race、double-free
  • 零成本抽象:高级语法在 Release 模式下编译为与 C 同质量的机器码
  • 表达力与可维护性:enum + pattern matching + trait + Result 使内核代码更清晰

1.2 内核 Rust 的设计目标(非废话版)

Rust-for-Linux 项目不是要重写整个内核——那是一个既不现实也无意义的目标。其实际目标是:

让新的内核代码(尤其是驱动程序)能够用 Rust 编写,同时与已有的 C 内核代码无缝互操作。

具体来说,覆盖了三类子系统:

子系统类型典型示例优先级
字符设备驱动简单的 misc device已合入 (6.1+)
网络驱动ASIX AX88772B USB 网卡已合入 (6.8+)
文件系统tarfs, ext2fs已合入 (6.12+)
GPU 驱动苹果 M1 GPU 初步支持开发中
块设备/NVMe高性能存储驱动规划中

二、内核 Rust 的架构模型

2.1 整体分层架构

┌──────────────────────────────────────────────────────────────────┐
│                    用户态应用程序                                 │
├──────────────────────────────────────────────────────────────────┤
│ ┌─────────────────┐  ┌──────────────────┐  ┌───────────────────┐  │
│ │  Rust 内核模块   │  │   C 内核模块      │  │  Rust 用户态驱动   │  │
│ │  (rust_*.ko)    │  │  (*.ko)         │  │  (ubdsrv 等)      │  │
│ └───────┬─────────┘  └───────┬──────────┘  └──────┬────────────┘  │
│         │    通过 C ABI/Generate 绑定交互            │              │
│ ┌───────▼──────────────────────────────────────────▼────────────┐ │
│ │              内核核心 (仍以 C 为主)                            │ │
│ │  mm/  net/  fs/  block/  drivers/  security/  lib/             │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │              Hardware (x86_64, ARM64, RISC-V, LoongArch)      │ │
│ └───────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

2.2 关键基础设施:The Rust Module Macro

内核中的 Rust 模块通过一个 module! 宏驱动——这是 Rust-for-Linux 提供的自定义语法扩展:

// 文件:/drivers/block/null_blk.rs
//! 以 Rust 实现的空块设备(示例性实现)

use kernel::prelude::*;
use kernel::block::mq;

module! {
    type: NullBlockDriver,        // 实现 trait 的类型
    name: "null_blk_rust",       // 模块名
    author: "Rust for Linux",
    description: "Rust version of null_blk",
    license: "GPL",
}

struct NullBlockDriver {
    _reg: Pin<Box<mq::Registration<NullBlockDriver>>>,
}

impl kernel::Module for NullBlockDriver {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        pr_info!("Rust null_blk 驱动加载成功\n");
        
        let reg = mq::Registration::try_pin_init(
            NullBlockDriver, 
            "nullb-rust"
        )?;
        
        Ok(Self { _reg: reg })
    }
}

impl mq::Operations for NullBlockDriver {
    fn request_callback(req: &mq::Request) -> Result {
        // 处理块设备 IO 请求
        req.complete(0)
    }
}

关键细节:Result<T> 是 Rust-for-Linux 提供的自定义类型,Pin<Box<T>> 用于固定内存防止自引用结构被移动。

2.3 自定义分配器:kmalloc vs vmalloc

Rust 内核模块可以通过封装自动使用内核分配器:

use kernel::alloc::{flags::GFP_KERNEL, Box, Vec};

// Box::try_new() → 底层调用 kmalloc()
let data = Box::try_new(MyStruct::default(), GFP_KERNEL)?;

// Vec::try_push() → 动态扩容,类似内核 krealloc()
let mut buffer = Vec::try_with_capacity(1024, GFP_KERNEL)?;
buffer.try_push(0xAB, GFP_KERNEL)?;

// 大内存分配使用 vmalloc
use kernel::alloc::flags::GFP_KERNEL;
let big_buf = Box::try_new([0u8; 1024 * 1024], GFP_KERNEL)?;

GFP 标志通过 Rust 的常量枚举封装:GFP_KERNEL、GFP_ATOMIC、GFP_NOIO——类型系统禁止错误组合(例如在原子上下文中使用 GFP_KERNEL 可能导致睡眠的分配)。

三、Rust ↔ C 互操作:bindgen 与 cbindgen

3.1 从 C 头文件生成 Rust 绑定

bindgen 将内核的 C 头文件实时转换为 Rust FFI 声明:

// 自动生成(不手写!)
// 输入:include/linux/mutex.h
// 输出:bindings.rs

extern "C" {
    #[link_name = "__mutex_init"]
    fn mutex_init(lock: *mut mutex);
    
    fn mutex_lock(lock: *mut mutex);
    fn mutex_unlock(lock: *mut mutex);
    fn mutex_trylock(lock: *mut mutex) -> c_int;
}

// 自动生成的 C 结构(repr(C))
#[repr(C)]
struct mutex {
    atomic_long: atomic_t,
    wait_list: list_head,
    // ... 省略
}

这些生成为 unsafe 的裸绑定,随后 kernel crate 在它们之上封装出安全的 Rust 抽象。

3.2 Safe Wrapper:从 unsafe C 到 safe Rust 的关键一层

这是 Rust-for-Linux 项目中最关键的分层——将内核 C API 封装为符合 Rust 安全约定的抽象:

// kernel/src/sync/mutex.rs

/// Safe wrapper around C struct mutex
pub struct Mutex<T: ?Sized> {
    mutex: Opaque<bindings::mutex>,
    data: UnsafeCell<T>,  // 内部可变性
}

impl<T> Mutex<T> {
    pub const fn new(data: T) -> Self {
        Self {
            mutex: Opaque::new(bindings::MUTEX_INITIALIZER),
            data: UnsafeCell::new(data),
        }
    }
    
    pub fn lock<'a>(&'a self) -> Guard<'a, T> {
        unsafe { bindings::mutex_lock(self.mutex.get()); }
        Guard { lock: self }
    }
}

pub struct Guard<'a, T: ?Sized + 'a> {
    lock: &'a Mutex<T>,
}

impl<T: ?Sized> Deref for Guard<'_, T> {
    type Target = T;
    fn deref(&self) -> &T {
        unsafe { &*self.lock.data.get() }
    }
}

impl<T: ?Sized> DerefMut for Guard<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        unsafe { &mut *self.lock.data.get() }
    }
}

impl<T: ?Sized> Drop for Guard<'_, T> {
    fn drop(&mut self) {
        unsafe { bindings::mutex_unlock(self.lock.mutex.get()); }
    }
}

这套封装的精妙之处在于:

  • 持有 Guard 才能访问数据 → CVE-2023-类型错误不出现在 Rust 侧
  • Guard 的 Drop 自动释放锁 → 不会出现忘 unlock 的 bug
  • 编译器静态保证不会 data race(!Sync 的内部可变性标记)
  • Opaque<T> 让 Rust 不假设 C 结构的布局,允许 C 侧修改而不破坏 Rust ABI

3.3 反向:从 Rust 导出 C 接口 (cbindgen)

当 Rust 模块需要向 C 核心暴露回调时,用 cbindgen 反向生成 C 头:

// Rust 侧
#[no_mangle]
pub extern "C" fn rust_driver_probe(dev: *mut device) -> c_int {
    pr_info!("Rust driver probed: {:?}\n", dev);
    0
}

#[no_mangle]
pub extern "C" fn rust_driver_remove(dev: *mut device) {
    pr_info!("Rust driver removed: {:?}\n", dev);
}

/* 自动生成的头文件(cbindgen 输出)
 * void rust_driver_probe(struct device *dev);
 * void rust_driver_remove(struct device *dev);
 ── C 驱动中可正常调用 */

四、实战:编写一个 Rust 网络设备驱动

4.1 驱动骨架结构

// drivers/net/usb/asix_rust.rs(简化版,基于真实已合入驱动)

use kernel::prelude::*;
use kernel::net::{self, Device, IrqStatus, NapiStruct};
use kernel::usb;

module! {
    type: AsixRustDriver,
    name: "asix772b_rust",
    author: "Rust-for-Linux",
    description: "ASIX AX88772B USB Ethernet (Rust)",
    license: "GPL v2",
}

#[pin_data]
struct AsixDriver {
    #[pin]
    dev: net::Device,
    #[pin]
    usb_dev: usb::Device,
    #[pin]
    napi: NapiStruct,
    #[pin]
    rx_buf: Vec<u8, GFP_KERNEL>,
}

impl usb::Driver for AsixRustDriver {
    const INFO: usb::DriverTable = usb::DriverTable::new()
        .match_flags(usb::ID_MATCH_VENDOR | usb::ID_MATCH_PRODUCT)
        .vendor(0x05ac)       // ASIX vendor ID
        .product(0x772b);     // AX88772B
    
    fn probe(dev: &usb::Device, _id: &usb::DeviceId) -> Result<impl PinInit<Self>> {
        pr_info!("ASIX AX88772B USB Ethernet 接入 (Rust 版)\n");
        
        // 自动注册网络设备
        let netdev = net::Device::new()?;
        netdev.set_mac_address([0x00, 0x50, 0xBF, 0x12, 0x34, 0x56])?;
        netdev.op_start_xmit(rust_start_xmit);
        netdev.op_open(rust_net_open);
        
        // RX 缓冲区用 Vec 管理 —— 释放后使用不可能发生
        let rx_buf = Vec::try_with_capacity(1518, GFP_KERNEL)?;
        
        // PinInit 保证初始化后结构不被移动(自引用安全)
        try_pin_init!(Self {
            dev: netdev,
            usb_dev: dev.clone(),
            napi: NapiStruct::new(),
            rx_buf,
        })
    }
    
    fn disconnect(dev: &Self) {
        pr_info!("ASIX 设备断开\n");
        // Rust Drop 自动清理资源,无需显式 free_netdev() / unregister_netdev()
    }
}

fn rust_start_xmit(skb: &mut SkBuff, dev: &net::Device) -> NetdevTx {
    let data = skb.data_ref();  // 借用检查保证 skb 在读取期间不会被释放
    // 发送到 USB 设备...
    usb_bulk_msg(dev.usb_dev, data);
    dev.stats.tx_bytes += data.len() as u64;
    NetdevTx::OK
}

fn rust_net_open(dev: &net::Device) -> Result {
    dev.napi_schedule();  // 借用检查阻止多线程同时调用
    Ok(())
}

4.2 对比 C 与 Rust 驱动的代码行数与安全性

维护维度C 驱动(以 AX88772B 为例)Rust 驱动
代码行数~1800 行~500 行
手动资源管理调用~35 处 kfree/free_netdev0 处(Drop 自动)
GFP 标志与上下文注释约定,编译器不检查类型系统静态检查
锁生命周期管理lock/unlock 配对需人工Guard 自动管理(RAII)
缓冲区边界检查人工 (CVE-高发区)编译期 + 运行期自动检查
线程安全保证无Send/Sync trait + 借用检查

五、Error Handling 在内核中的特殊处理

5.1 Result<T> 与内核错误码

内核使用负数作为错误码(如 -ENOMEM = -12),Rust-for-Linux 的 Result 实现了自动编解码:

use kernel::error::code::*;

// Rust Result 与内核 errno 映射
fn rust_alloc_device() -> Result> {
    let ptr = unsafe { bindings::alloc_netdev(0, b"rust%d\x00" as *const u8) };
    if ptr.is_null() {
        return Err(-ENOMEM);  // 编译为返回 -12
    }
    // Device 自动封装为安全的 Box<>
    Ok(Box::try_from_raw(ptr, GFP_KERNEL)?)
}

// 调用侧自动解包
fn init_module() -> Result {
    let dev = rust_alloc_device()?;  // ? 操作符自动传播错误
    /* 成功时使用 dev */
    Ok(())
    // 函数返回 Err(-ENOMEM)? → 自动向上传播,不跳过清理
    // 因为 Drop trait 保证了中间资源的自动释放
}

5.2 从 C 回调中转换错误

当编写被 C 核心调用的回调时,需要从 C 错误码转回 Rust Result:

// C 调用 Rust 的回调
#[no_mangle]
pub extern "C" fn rust_driver_ioctl(dev: *mut device, cmd: u32, arg: u64) -> c_int {
    // to_result() 将 C 负错误码转换为 Result Err
    let result: Result<()> = (|| {
        if cmd == MY_IOCTL_WRITE {
            let data = unsafe { 
                // UserSlicePtr 保证用户态指针安全(不发生 kernel Oops)
                UserSlicePtr::new(arg as *mut u8, 1024).read() 
            }?;
            process_data(&data)?;
        }
        Ok(())
    })();
    
    match result {
        Ok(()) => 0,
        Err(e) => e.to_errno() as c_int,
    }
}

UserSlicePtr 是 Rust-for-Linux 提供的关键安全封装——它确保用户态传入的指针在内核中被安全地解引用,而不像 C 中容易遗漏 copy_from_user() 而成为漏洞来源。

六、并发安全:Rust 的所有权如何取代内核锁

6.1 Mutex 之外的无锁路径

在合适的场景下,Rust 的借用规则天然防止 data race,使某些代码完全无需锁:

// 场景:只读访问全局只初始化一次的数据
use kernel::sync::Arc;

struct DriverConfig {
    vendor_id: u16,
    max_transfer_size: usize,
    features: FeatureFlags,
}

static CONFIG: DriverConfig = DriverConfig {
    vendor_id: 0x05ac,
    max_transfer_size: 4096,
    features: FeatureFlags::all(),
};

// CONFIG 是不可变静态变量,&引用天然线程安全
// Rust 编译器保证:多个线程 &CONFIG 不会产生 data race
fn get_vendor_id() -> u16 {
    CONFIG.vendor_id  // 零开销:编译器知道它不可变,完全跳过原子操作
}

6.2 Arc<T> 替代引用计数 + 手动 kref_get

C 内核常用 kref 实现对象的引用计数 + 自动释放:

// C 版本(手动管理,容易出错)
struct my_object {
    struct kref refcount;
    void (*release)(struct my_object *);
};

void my_object_put(struct my_object *obj) {
    kref_put(&obj->release);  // 忘记调 → 内存泄漏
}

// Rust 版本(编译期保证)
use kernel::sync::Arc;

struct MyObject {
    data: [u8; 1024],
}

let obj1 = Arc::try_new(MyObject { data: [0; 1024] }, GFP_KERNEL)?;
let obj2 = obj1.clone();  // 引用计数 +1,不可能写错

// Drop 自动归零后调用 release
drop(obj1);  // 引用计数 -1,若为零 → 释放内存
drop(obj2);  // 现在为零,内存自动释放

6.3 Spinlock 与 Mutex 的类型区分

包装 trait 强制在上下文中选择正确锁:

// 自旋锁:中断上下文(GFP_ATOMIC)
let value = spinlock::Spinlock::new(0u64);
value.lock();  // 抢占禁用,禁止在中断下半部之后使用

// 互斥锁:进程上下文(可睡眠)
let data = mutex::Mutex::new(MyStruct::new());
data.lock();  // 可能睡眠!中断上下文不能用 ← 编译期无法区分所有上下文,
              // 但 Rust 的约定 + 静态分析工具(patch review)可以发现此问题

七、性能开销基准

7.1 编译产物对比

Release 优化模式下 Rust 驱动 vs C 驱动的代码质量对比:

维度Rust (opt-level=3)C (-O2)
指令执行路径相同相同
边界检查开销编译期消除(LLVM 优化)无(但可能越界)
panic 处理分支同 C 的 BUG_ON相似
动态分配调用链Box → kmalloc(同 C)kmalloc
运行速度98-100%(等同于 C)基准
二进制大小+5-15%(调试信息 + PLT)基准

在实际 ASIX 网络驱动测试中,Rust 版与 C 版的网络吞吐量差异在 2% 以内(在测量误差范围内),IO 路径的 CPU 占用基本完全相同。

7.2 唯一可测量的开销:编译时间

Rust 编译比 C 慢——这是一个需要坦诚的事实:

# 编译时间对比(干净构建,4C8T 机器)
C 内核模块(Kbuild):     ~30-60 秒
Rust 内核模块(cargo):   ~3-5 分钟(首次)
Rust 内核模块(增量):    ~10-30 秒

# 差异原因:
# 1. Rust 要编译整个 libcore + alloc + kernel crate → 不重复
# 2. bindgen 解析头文件 → 缓存后快速
# 3. LLVM 后端优化 -O3 → 同 C
# 4. 增量编译缓存大幅降低后续编译时间

八、当前状态盘点与贡献者指南

8.1 已合入主线的 Rust 驱动(截至 2025 年底)

内核版本驱动/子系统说明
6.1rust_random, rust_chrHello World + 字符设备模板
6.8asix AX88772B完整 USB 以太网驱动
6.10qrtr(Qualcomm 路由)网络驱动
6.12tarfs, ext2 (只读)文件系统
6.12Nova (苹果 M1 GPU)实验性 GPU 驱动框架
6.15+NVMe 驱动 (开发中)高性能块设备

8.2 源码结构

rust/                          # 顶层 Rust 支持
├── kernel/                    # 内核 Rust 核心 crate
│   ├── lib.rs                 # crate 根 + prelude
│   ├── prelude.rs             # 常用导入自动引入
│   ├── sync/                  # Mutex, Spinlock, Arc, CondVar...
│   ├── alloc/                 # Box, Vec, 内核分配器封装
│   ├── io_buffer.rs           # IoBufferVec, KVec
│   ├── types.rs               # Opaque, ScopeGuard, ARef
│   ├── delay.rs               # msleep, usleep_range 封装
│   ├── error.rs               # Result, 错误码编解码
│   └── str.rs                 # 内核 Cstr, fmt 支持
├── bindings/                  # 自动生成的 C 绑定
│   ├── bindings_generated.rs  # bindgen 输出
│   └── bindings_helper.rs     # 手动辅助包装
├── macros/                    # proc_macro 定义
│   ├── module.rs              # module! 宏
│   ├── vtable.rs              # vtable! 宏
│   └── pin_data.rs            # pin_data! 宏
├── helpers/                   # C 侧辅助函数
│   ├── helpers.c              # 调用的 C 辅助实现
│   └── rust_helper.h          // 头文件
└── uapi/                      // 用户态 ABI 兼容
    └── lib.rs

8.3 开发环境搭建

# 1. 需要 Rust nightly(建议使用 rustup)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rust-init.rs | sh
rustup default nightly
rustup component add rust-src rustfmt clippy

# 2. 下载内核源码
git clone https://github.com/Rust-for-Linux/linux.git -b rust-next
cd linux

# 3. 配置内核启用 Rust
make LLVM=1 rustavailable    # 检查 Rust 是否可用
make LLVM=1 menuconfig       # 启用 CONFIG_RUST=y

# 4. 编译示例驱动
make LLVM=1 drivers/block/null_blk.o   # C 版
make LLVM=1 drivers/block/null_blk.rsi.o  // Rust 版

# 5. 新建驱动参考模板
cat > drivers/net/my_rust_driver.rs << 'EOF'
//! My Rust Network Driver
use kernel::prelude::*;
use kernel::net;
module! {
    type: MyDriver,
    name: "my_rust_net",
    author: "Your Name",
    description: "My first Rust kernel driver",
    license: "GPL",
}
struct MyDriver { _pin: Pin> }
impl kernel::Module for MyDriver {
    fn init(_: &'static ThisModule) -> Result {
        pr_info!("Rust driver loaded!\n");
        Ok(Self { _pin: Pin::from(Box::try_new(net::Device::new()?)?) })
    }
}
EOF

九、存在的挑战与应对

9.1 unsafe 代码量——坦诚面对

Rust 内核模块仍有 unsafe 代码。与用户态 Rust 不同,内核模块不可能完全消除 unsafe,原因包括:

  • 所有硬件 MMIO 访问都是 unsafe
  • 与 C 的 FFI 调用都是 unsafe
  • 修改物理页表、DMA 操作本质上不安全

Rust-for-Linux 的策略:将 unsafe 约束到 Safe wrapper 内部,驱动开发者不直接写 unsafe。理想状态:新驱动 95%+ 的代码是 safe Rust,仅 kernel/ crate 维护需要 unsafe。

9.2 与 C代码的交叉编译和 LTO

Rust 使用 LLVM 后端,GCC 构建的 kernel 需要 LLVM 联编。LLVM=1 make 参数启用全 LLVM 工具链,配合 CONFIG_LTO_CLANG=y 获取跨语言 LTO 优化——实际上 Rust 驱动函数可以被 LTO 内联进 C 调用站点,完全无 ABI 开销。

9.3 API 不稳定性

注意:内核 Rust API 在 6.x 中仍在快速迭代。kernel/ crate 的 API 改变需要对所有消费方做补丁——这意味着使用 Rust 的内核模块可能在升级内核时需要跟进修改。这是"还在成熟中"的正常代价。

十、总结

Rust 进入 Linux 内核不是突发奇想,而是系统编程语言演进的必然方向。从 Linus 早期公开信的"我仍不太喜欢"到将支持合入主线,从"先做最简单的字符设备"到"USB 网卡驱动全功能",Rust-for-Linux 用工程实践回答了一个核心问题:内存安全与高性能不可兼得?不,它们可以。

对于系统程序员,这是二十年来最重要的范式转变。对于普通用户,这意味着 Linux 内核从此开始——虽然缓慢地——切断那最顽固的漏洞来源。正如项目维护者 Ojeda 所说:

"我们不是在重写 Linux 内核,我们是在为下一代内核开发者配上更安全的工具。"

如果你正在考虑编写新的内核驱动、文件系统或网络协议栈——现在是开始学习内核 Rust 的最佳时机。

参考资料:

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论