☰
Rust 写 Linux 驱动:Linus 观望背后的技术考量与实战避坑
2026/9/26 1:39:11 网站建设 项目流程

1. 这场争论到底在吵什么

先把时间线拉回到那个邮件列表里的经典回合。Linus 那句“感兴趣,但保持观望”其实不是一句客套话,而是一个在操作系统内核圈摸爬滚打三十多年的人,对一门新语言进入核心地带时最本能的反应。Rust 想进 Linux 内核写驱动,这件事从 2020 年前后就开始被反复讨论,到后来 Rust for Linux 项目真正落地、部分驱动开始用 Rust 重写,整个社区的态度一直是“可以试,但别急着全面铺开”。

我自己是从 2018 年开始接触 Rust 的,那时候还在用 C 写一些嵌入式的东西,后来陆续在用户态工具、网络服务里用 Rust 替换掉了一些老代码。2022 年之后开始认真看 Rust for Linux 的进展,也动手编译过带 Rust 支持的 kernel,试着写过一个最简单的字符设备驱动。所以这个话题对我来说不是纸上谈兵,是真金白银踩过坑的。

这篇文章想聊清楚几件事:Rust 写 Linux 驱动到底难在哪、Linus 的“观望”背后有哪些技术考量、如果你是一个想上手的开发者,现在应该怎么起步、以及在实际操作中会遇到哪些文档里不会写的坑。适合的人群很明确:有 C 语言基础、对 Linux 内核或驱动开发有兴趣、想评估要不要把 Rust 纳入技术栈的工程师。完全没碰过内核的新手也能看,但需要你先补一些操作系统和 C 的基础。

核心关键词我先自然带出来:Rust、Linux、驱动、Linus。这四个词构成了整个讨论的骨架——一门内存安全的系统级语言,一个庞大的开源内核,一类直接跟硬件打交道的代码,以及一个以直言不讳著称的最终决策者。

2. 为什么 Rust 进内核这件事这么敏感

2.1 内核驱动开发的“原罪”:C 语言的内存模型

Linux 内核用 C 写了三十多年,这不是没有原因的。C 语言贴近硬件、没有运行时、编译产物可控、跟汇编的互操作几乎零成本,这些特性让它成为操作系统内核的天然选择。但 C 的问题也同样明显:手动内存管理、指针裸奔、没有所有权概念,导致内核里大量的 bug 都是内存安全问题——空指针解引用、缓冲区溢出、释放后使用、双重释放。

我印象很深的一个数据是,在 Linux 内核历年的 CVE 里,内存安全类问题长期占据相当大的比例。这不是说内核开发者水平不行,而是 C 语言本身不提供任何编译期的保护。你写kfree(ptr)之后如果还有别的地方引用ptr,编译器不会拦你,运行时也不会拦你,直到某个深夜系统 panic 了才暴露出来。

Rust 的核心卖点恰好打在这个痛点上。它的所有权系统、借用检查器、生命周期标注,在编译期就能把大部分内存安全问题挡在门外。你不需要 GC,不需要运行时,编译出来的机器码性能跟 C 基本在同一量级。这就是为什么 Rust 会被认为是“唯一有资格挑战 C 在内核地位的语言”。

2.2 Linus 的“观望”不是拒绝,是工程理性

很多人把 Linus 的态度解读成“他不喜欢 Rust”,这是误读。他的原话是感兴趣但保持观望,这在内核维护者的语境里其实是一种相当开放的表态。内核社区对新东西的默认态度是“先证明你能活下来再说”,从 devfs 到 systemd 到 BPF,每一个新机制进入内核都经历过漫长的观望期。

Linus 的顾虑我理解下来主要有几层。第一是维护成本:内核是一个有几千名贡献者、几十个子系统的巨型项目,引入一门新语言意味着维护者要同时懂 C 和 Rust,代码审查的复杂度上升。第二是工具链成熟度:Rust 编译器、绑定生成工具、内核构建系统对 Rust 的支持,都需要时间打磨。第三是生态惯性:现有的驱动、子系统、调试工具全是围绕 C 建立的,Rust 驱动要融入这套体系,需要大量的胶水代码。

提示:理解 Linus 的态度,关键不是看他说了什么,而是看内核社区实际做了什么。Rust for Linux 项目一直在推进,部分驱动已经合入主线,这说明“观望”不等于“封杀”。

2.3 Rust for Linux 项目的真实进展

从公开的信息看,Rust 在内核里的落地是分阶段走的。最早是基础设施的引入——让内核构建系统能编译 Rust 代码、提供core和alloc的适配、建立 C 和 Rust 之间的 FFI 绑定。然后是抽象层的建设,把内核的各种 API 用 Rust 的安全接口包装起来,比如Arc、Mutex、SpinLock这些。再往后才是具体的驱动实现。

目前已经能看到一些用 Rust 写的驱动进入主线,主要集中在字符设备、网络设备、以及一些平台驱动上。Android 和部分发行版也在推动 Rust 在内核里的使用。但要说“Rust 取代 C 写驱动”,那还差得远,现在的状态更像是“Rust 可以写驱动了,但大部分驱动还是 C”。

3. 用 Rust 写驱动的核心技术点拆解

3.1 从 C 到 Rust:FFI 绑定是怎么工作的

Rust 和 C 能互操作,靠的是FFI(Foreign Function Interface)。内核里的 C 函数,通过bindgen这类工具自动生成 Rust 的声明,让 Rust 代码能直接调用。反过来,Rust 写的函数加上#[no_mangle]和extern "C",也能被 C 代码调用。

但这里有个关键问题:自动生成的绑定是unsafe的。因为 C 那边不保证任何内存安全,Rust 编译器无法验证跨语言调用的正确性。所以 Rust for Linux 做了一件很重要的事——在 unsafe 的原始绑定之上,构建一层safe abstraction。比如内核的struct file_operations,原始绑定是一堆函数指针,Rust 这边会把它包装成一个 trait,让你实现 trait 而不是直接填函数指针。

// 简化的示意,实际内核 API 更复杂 impl FileOperations for MyDriver { fn open(inode: &Inode, file: &File) -> Result<()> { // 安全代码 Ok(()) } fn read(file: &File, buf: &mut [u8], offset: u64) -> Result<usize> { // 安全代码 Ok(0) } }

这层抽象的价值在于,驱动开发者写的大部分代码是 safe Rust,编译器帮你检查内存安全。只有真正跟硬件寄存器打交道、或者调用未包装的 C API 时,才需要写unsafe块。这比全篇 C 代码裸指针满天飞要安全得多。

3.2 内核里的 Rust 没有标准库,只有 core 和 alloc

这是新手最容易踩的坑。你在用户态写 Rust,std::里什么都有,Vec、String、Box、文件 IO、线程,随便用。但内核里没有std,因为内核不能依赖操作系统的服务——它本身就是操作系统。

Rust for Linux 提供的是core(无堆分配的基础功能)和alloc(需要堆分配的功能,比如Vec、Box)。但即便是alloc,也需要内核先初始化好分配器。而且内核里的分配是GFP 标志控制的,GFP_KERNEL可以睡眠,GFP_ATOMIC不能睡眠,用错了会在中断上下文里出大问题。

// 内核里分配内存需要指定标志 let v = KVec::with_capacity(n, GFP_KERNEL)?;

我实测下来,从用户态 Rust 转到内核 Rust,最大的思维转变就是:你不能再假设有无限的内存、不能再假设可以随便阻塞、不能再假设有现成的容器和工具。每一样东西都要想清楚它在内核上下文里是否可用。

3.3 并发模型:Rust 的所有权如何映射到内核锁

内核开发绕不开并发。自旋锁、互斥锁、RCU、原子操作,这些在 C 里都是靠约定和纪律来保证正确性的。Rust 的所有权系统在这里能发挥很大作用——它可以把“持有锁才能访问数据”这件事编码到类型系统里。

Rust for Linux 的做法是,把锁和数据绑定在一起。比如Mutex<T>里包着T,你要访问T必须先拿到锁的 guard。guard 的生命周期结束时锁自动释放,编译器保证你不会忘记解锁。这比 C 里手动spin_lock/spin_unlock要可靠得多,尤其是在有多个返回路径的函数里。

let data = Mutex::new(0); { let mut guard = data.lock(); *guard += 1; } // guard 离开作用域,锁自动释放

但这里也有坑。内核里的锁有各种变体,SpinLock、Mutex、RwLock、RwSemaphore,还有中断上下文里的spin_lock_irqsave。Rust 的抽象层需要覆盖这些场景,而不同的锁在 Rust 里的使用方式有细微差别。用错了不会编译报错,但运行时会死锁或者更糟。

4. 动手实操:从零编译一个带 Rust 支持的内核

4.1 环境准备与工具链安装

先说清楚,这一步的复杂度不低,我建议在虚拟机或者一台可以随便折腾的机器上做。你需要的东西包括:一个 Linux 发行版(Ubuntu 22.04 或更新版本比较省事)、Rust 工具链、内核源码、以及一堆构建依赖。

Rust 的安装用rustup最方便,但要注意内核对 Rust 版本有要求,太新或太旧都可能编译不过。Rust for Linux 通常会指定一个最低版本和推荐版本,装之前先查一下当前内核源码的Documentation/rust/quick-start.rst。

# 安装 rustup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 安装内核需要的组件 rustup component add rust-src rustup component add llvm-tools-preview # 安装 bindgen cargo install --locked bindgen-cli

构建依赖这块,flex、bison、libssl-dev、libelf-dev、bc、dwarves这些是标配。Ubuntu 下一条命令搞定:

sudo apt install build-essential flex bison libssl-dev libelf-dev bc dwarves

注意:bindgen依赖libclang,如果编译时报找不到 clang,需要额外装libclang-dev。这个坑我在两台机器上都遇到过,文档里往往一笔带过。

4.2 获取内核源码并开启 Rust 支持

内核源码从官方仓库拉,建议用稳定版或者 Rust for Linux 的维护分支。直接拉 mainline 也可以,但可能遇到正在开发中的不稳定代码。

git clone --depth=1 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git cd linux

配置内核的时候,关键是打开 Rust 相关的选项。用make menuconfig进图形界面,在General setup里找到Rust support,打开它。然后确保CONFIG_RUST相关的子选项也开了。

make menuconfig # General setup -> Rust support -> 选中

如果你想像我一样用命令行快速配置,可以直接改.config:

scripts/config --enable CONFIG_RUST scripts/config --enable CONFIG_RUST_IS_AVAILABLE

配置完之后make -j$(nproc)开始编译。第一次编译带 Rust 的内核会比较慢,因为要编译 Rust 的 core 和 alloc 库。我实测在一台 8 核的机器上大概要 20 到 30 分钟,取决于配置。

4.3 写一个最小的 Rust 字符设备驱动

内核编译通过之后,可以试着写一个最简单的 Rust 驱动。Rust for Linux 提供了module!宏来定义模块的元信息,file_operations的 trait 来实现文件操作。

// 简化的最小驱动骨架 use kernel::prelude::*; module! { type: MyRustDriver, name: "my_rust_driver", author: "Your Name", description: "A minimal Rust char driver", license: "GPL", } struct MyRustDriver; impl kernel::Module for MyRustDriver { fn init(_module: &'static ThisModule) -> Result<Self> { pr_info!("my_rust_driver loaded\n"); Ok(MyRustDriver) } } impl Drop for MyRustDriver { fn drop(&mut self) { pr_info!("my_rust_driver unloaded\n"); } }

这个骨架只做了一件事:加载时打印一行日志,卸载时打印一行日志。但它是验证整个工具链是否打通的最好方式。编译成.ko之后,用insmod加载,dmesg看日志,rmmod卸载。

make M=drivers/my_rust_driver sudo insmod my_rust_driver.ko dmesg | tail sudo rmmod my_rust_driver

提示:如果insmod报Invalid module format,大概率是内核版本和编译时的版本不一致,或者 Rust 支持没编译进去。先uname -r确认当前内核,再检查.config里的CONFIG_RUST。

4.4 参数选择与性能考量

写驱动绕不开性能。Rust 编译出来的代码在大多数场景下跟 C 持平,但在某些边界情况下会有差异。比如 Rust 的panic机制,默认会展开栈,但内核里必须用panic=abort,因为内核没有用户态的栈展开基础设施。这个在编译选项里要配好。

另一个是内联。C 里static inline函数很常见,Rust 里对应的是#[inline]或者#[inline(always)]。但 Rust 的跨 crate 内联需要 LTO(Link Time Optimization),内核构建里要确保 LTO 配置正确,否则性能会有损失。

内存布局也是要考虑的。Rust 的repr(Rust)默认不保证字段顺序,跟 C 交互的结构体必须用#[repr(C)]。这个如果忘了,会出现非常诡异的内存错乱,而且很难调试。

5. 常见问题与排查技巧实录

5.1 编译期问题速查

问题现象可能原因解决方法
error: Rust compiler not foundrustc 不在 PATH 或版本不符用 rustup 安装指定版本,检查rustc --version
error: could not find libclangbindgen 依赖缺失安装libclang-dev,设置LIBCLANG_PATH
error: unresolved import kernel::内核 Rust 抽象层未编译确认CONFIG_RUST已开启并重新编译
error[E0658]: feature not stable用了 nightly 特性但工具链是 stable切换到内核要求的工具链版本
Invalid module format模块与内核版本不匹配用当前运行内核的源码编译,检查vermagic

5.2 运行期问题与排查思路

运行期的问题比编译期更难查,因为内核里没有println!调试,只能靠pr_info!、pr_err!这些宏往dmesg里打日志。我踩过的一个坑是,在中断上下文里调用了会睡眠的分配函数,结果系统直接卡死,日志都来不及打。

排查这类问题的思路是:先确认问题发生在哪个上下文(进程上下文、中断上下文、软中断),再检查这个上下文里允许做什么。内核文档里对每个 API 的上下文限制都有说明,但很分散,需要慢慢积累。

另一个常见问题是引用计数。Rust 的Arc在内核里对应kref,但内核的引用计数规则比用户态复杂,有些对象在特定路径下不能增加引用。用 Rust 的Arc包装内核对象时,要确保生命周期跟内核的规则一致,否则会出现对象提前释放或者永远不释放。

5.3 独家避坑经验

第一条,不要一上来就写复杂驱动。我见过不少人直接拿一个网络驱动或者块设备驱动来改,结果被各种子系统 API 淹没。正确的路径是先写一个什么都不做的模块,再写一个字符设备,再写一个 platform driver,一步步来。

第二条,善用 QEMU 做测试。在真机上调试内核驱动,一旦 panic 就要重启,效率极低。用 QEMU 跑一个带 Rust 支持的内核,配合gdb调试,可以大幅提升效率。QEMU 的-s -S参数可以让内核启动时暂停,等 gdb 连接。

qemu-system-x86_64 -kernel arch/x86/boot/bzImage \ -append "console=ttyS0" -nographic -s -S

第三条,关注邮件列表和 Rust for Linux 的仓库。这个领域变化很快,文档往往滞后。很多最新的 API 用法、已知问题、临时解决方案,都是在邮件列表和 PR 讨论里出现的。我订阅了 Rust for Linux 的邮件列表,每周花半小时扫一遍,能省下大量自己摸索的时间。

第四条,C 和 Rust 的边界要画清楚。不是所有代码都适合用 Rust 重写。中断处理的最底层、跟汇编强相关的部分、性能极度敏感的路径,可能还是 C 更合适。Rust 的优势在于逻辑复杂、状态多、容易出内存错误的地方。把 Rust 用在刀刃上,而不是为了用而用。

6. 这件事对开发者的实际影响

6.1 技能栈的重新评估

如果你是一个 Linux 驱动工程师,现在要不要学 Rust?我的判断是:值得学,但不用急着转。C 在内核里的地位短期内不会动摇,现有的驱动岗位绝大多数还是 C。但 Rust 在内核里的份额在增长,尤其是新硬件、新子系统的驱动,用 Rust 写的比例会越来越高。提前掌握这门技能,在未来几年会有明显的先发优势。

学习路径我建议这样走:先用 Rust 写用户态的程序,熟悉所有权、生命周期、trait、错误处理这些核心概念。然后看 Rust for Linux 的文档和示例代码,理解内核里的 Rust 跟用户态有什么不同。最后动手编译内核、写简单驱动。整个过程快的话两三个月能入门,但要达到能独立写生产级驱动的水平,需要更长时间的积累。

6.2 对国内开发环境的影响

国内做 Linux 内核和驱动开发的团队不少,主要集中在芯片原厂、设备厂商、以及一些做操作系统的公司。Rust 在内核里的推进,对这些团队来说是一个需要关注的变量。一方面,Rust 能降低内存安全类 bug 的比例,减少调试和维护成本;另一方面,团队需要投入时间学习新语言、搭建新工具链,短期内是成本。

我了解到的一些团队已经在做技术预研,用 Rust 写一些非核心的驱动模块,积累经验。这个策略我觉得是理性的——不激进替换,但保持跟进,等生态成熟了再加大投入。

6.3 生态与工具链的现状

Rust 在内核里的工具链还在完善中。bindgen生成绑定的质量参差不齐,有些复杂的 C 宏和类型定义处理不好,需要手动调整。调试工具方面,gdb对 Rust 的支持还可以,但内核里的 Rust 调试体验还比不上 C。性能分析工具perf对 Rust 符号的解析基本可用,但偶尔会有符号丢失的情况。

这些工具链问题会随着时间改善,但现阶段如果你要上生产,需要做好心理准备:会遇到一些文档里没写的问题,需要自己去邮件列表或者 issue tracker 里找答案。

7. 我个人的一些判断

Rust 写 Linux 驱动这件事,我的看法是方向明确但节奏缓慢。Linus 的观望态度是合理的,内核这种级别的项目,任何重大变更都需要用年来衡量。Rust for Linux 从提出到有驱动进入主线,花了好几年,接下来从“能写”到“好用”再到“主流”,可能还需要更长时间。

但趋势是清楚的。内存安全这个问题,C 语言本身解决不了,靠代码审查和静态分析工具也只能缓解。Rust 提供了一条在编译期就消除大部分内存安全问题的路径,这对内核这种对稳定性要求极高的软件来说,价值是巨大的。现在的问题不是“要不要用 Rust”,而是“多快能用起来”。

对于想上手的开发者,我的建议是保持耐心,从小处着手。先写一个能加载卸载的空模块,再写一个字符设备,再尝试 platform driver。每进一步都确保理解了背后的原理,而不是照抄示例。内核开发没有捷径,Rust 只是换了一把更好用的工具,该懂的硬件知识、并发模型、内存管理,一样都不能少。

最后分享一个我自己的习惯:每次写驱动之前,先在纸上画出数据流和生命周期图,标清楚哪些对象在哪个上下文里被创建、被引用、被释放。这个习惯在 C 里能帮你少写 bug,在 Rust 里能帮你更好地理解所有权和借用规则。工具会变,但把问题想清楚这件事,永远不会过时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询