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_netdev | 0 处(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.1 | rust_random, rust_chr | Hello World + 字符设备模板 |
| 6.8 | asix AX88772B | 完整 USB 以太网驱动 |
| 6.10 | qrtr(Qualcomm 路由) | 网络驱动 |
| 6.12 | tarfs, ext2 (只读) | 文件系统 |
| 6.12 | Nova (苹果 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 的最佳时机。
参考资料:

发表评论 取消回复