Driver Core 的三层改造Linux 7.0 完成了一件内核社区争论 6 年的里程碑事件Rust 从 EXPERIMENTAL 毕业成为稳定特性。首个受益的子系统是内核中最庞大也最脆弱的Driver Core本次改造的核心约束是C 代码一行不改Rust 通过三层抽象改造成功嵌入 30 年历史的老代码基座。一、背景1.1 Driver Core 的现状将 Driver Core 比作一栋运行了 30 年的老楼组件比喻Rust 的解决方案kobject/kref楼内水管系统外层套ArcDevice自动记账sysfs属性布告栏加装电子屏类型安全probe/remove房客入住退房门上换智能锁RAII老楼的三大问题水管要手动记账漏水没人管漏一次加内存泄露漏一次减 use-after-free布告栏谁都能乱贴类型不安全裸指针操作房客退房钥匙经常收不回来资源清理代码复杂易出错1.2 三位人物Miguel Ojeda角色Rust for Linux 项目发起人、核心维护者贡献2020 年发出第一封 RFC6 年后负责编写每一行 C 封装代码铁律Rust 代码里除unsafe块外不允许出现任何未定义行为Greg KH角色Driver Core 维护者struct device守护人20 年转变从最初保留态度 → 最终点头同意关键作用7.0 合并时亲自 review 每一行 Rust binding扮演守门人角色Linus Torvalds态度转变从允许试试看 → 7.0 发布邮件首次使用graduated毕业描述 Rust铁律Rust 代码绝不能阻碍 C 代码的重构C 侧维护者想动结构就动结构Rust 侧重新生成 binding责任边界清清楚楚二、架构分层设计2.1 四层架构总览┌─────────────────────────────────────────────────────────┐ │ 驱动开发者面向的世界Safe API │ ├─────────────────────────────────────────────────────────┤ │ rust/kernel手写 Safe 封装层 │ │ ArcDevice | Attribute | Device 等等 │ ├─────────────────────────────────────────────────────────┤ │ rust/bindingsbindgen 自动生成约 10 万行 │ │ 从 include/linux/device.h 自动生成 │ ├─────────────────────────────────────────────────────────┤ │ C 侧 Driver Core原封不动 │ │ struct device | kobject | sysfs 核心代码 │ └─────────────────────────────────────────────────────────┘2.2 架构设计原则层级特性说明越往下越unsafeC 互操作层越往上越safe开发者友好边界零成本抽象#[repr(transparent)]保证2.3 bindgen 的作用机制内核 build 时 include/linux/device.h ↓ bindgen 自动解析 ↓ rust/bindings/devices.rs~10万行 ↓ Rust 侧开发者不碰此文件特性C 侧结构体加字段 → bindgen 次日自动同步 → Rust 侧编译报错 → Rust 维护者修 binding三、第一层改造引用计数自动化3.1 C 侧的古老问题// C 代码中的手动引用计数管理structdevice*devkobject_get(parent-kobj);// ... 使用 dev ...kobject_put(parent-kobj);常见 bug 类型kobject_get后忘记kobject_put→ 内存泄露kobject_put后继续使用 → use-after-free3.2 Rust 的解决方案// 灵魂代码所有权与引用计数的缝合#[repr(transparent)]pubstructDevice{// C 的 struct device 在内存中与此 Rust struct 完全重叠_private:[u8;0],}unsafeimplRefCountedforDevice{// 告诉编译器此类型天生带引用计数// 克隆时自动调 kobject_get// Drop 时自动调 kobject_put}3.3 解释特性作用#[repr(transparent)]RustDevice与 Cstruct device内存布局完全等价指针可零成本互转unsafe impl RefCounted声明此类型使用引用计数机制自动行为克隆时调用kobject_getDrop 时调用kobject_put效果开发者写 Rust 代码完全不用关心kref底层走的还是 C 那套机制但编译期保证不错漏。四、第二层改造sysfs 属性类型安全4.1 C 侧的问题// C 的 show 函数签名ssize_t(*show)(structdevice*dev,structdevice_attribute*attr,char*buf);// 问题kobj 是裸指针类型不明确// 问题buf 需要手动处理 buffer overflow// 问题device_attribute 回调地狱4.2 Rust 的解决方案// sysfs 属性的 trait 定义pubtraitAttribute{typeParent;// 类型由编译器推导防止混淆fnshow(self,parent:Self::Parent)-FormatterResult;fnstore(self,parent:mutSelf::Parent,value:[u8])-Result;}// FormatterResult 自动处理 buffer 长度// [u8] 替代裸 char*编译期检查边界4.3 改进效果C 侧Rust 侧struct kobject *裸指针Device类型安全引用char *buf手动管理FormatterResult自动 buffer 处理整类 SYSFS 相关 bug从语言层面消失五、第三层改造probe/remove 生命周期 RAII 化5.1 C 侧的典型问题// C 的 probe 函数常见结构intprobe(...){if(alloc_a()!0)gotoerr_a;if(alloc_b()!0)gotoerr_b;if(alloc_c()!0)gotoerr_c;return0;err_c:free_b();err_b:free_a();err_a:return-ENOMEM;}// 资源越多goto 标签越多// 30 年驱动中最经典的 bug 来源5.2 Rust 的 RAII 机制// probe 函数pubfnprobe(pdev:mutPlatformDriver)-Result(){letresource_aResourceA::new()?;letresource_bResourceB::new()?;letresource_cResourceC::new()?;// 注册资源...Ok(())// 函数结束所有局部变量按后进先出顺序 drop// 编译器自动生成回滚代码}// remove 函数 - 仅两行有效代码fnremove(dev:mutDevice){letboxeddev.data::BoxMyDriverState().remove();// boxed 在函数作用域结束时自动 drop// 所有持有的资源全部释放}5.3 效果对比C 侧Rust 侧probe 中 N 个资源 N 个 goto 标签 N 个清理函数probe 中声明即管理作用域结束自动清理remove 中手写几十行清理代码两行有效代码取回 Box 自动 drop清理顺序依赖程序员保证后进先出LIFO编译期保证意义不是语法糖是把语义级别的清理上升为**语言机制**。六、C/Rust 互操作机制6.1 共存原理qRust 驱动和 C 驱动能共存吗a能注册到同一条platform_bus。platform_bus │ ├── C 驱动传统 │ └── 符号表导出函数指针 │ └── Rust 驱动 └── 通过 extern C 暴露符合 C ABI 的接口 └── 函数指针注册到 bus6.2 技术// Rust 驱动暴露给 C 的接口#[no_mangle]pubexternCfnmy_rust_driver_probe(dev:*mutc_void,id:*constc_void)-c_int{// FFI 边界unsafe 块内操作// 调用 safe 封装层}关键extern C确保函数签名兼容 C ABI底层 bus 只看到函数指针不知道下游是 C 还是 Rust事件流bus_call_probe→ 函数指针 → Rust/C 各自处理6.3 设备生命周期0ms ─── 总线发现设备 2ms ─── C 侧 probe 入口 4ms ─── Rust 侧获取 Device 引用 6ms ─── 注册 sysfs 属性 8ms ─── 设备 Ready ... 设备稳态运行 ... 拔除触发 remove() ──→ Box::drop() ──→ 资源逐层回收 ──→ kref_put() ──→ C 侧 release()本质Rust 接管的是**状态转换的时机**——从人脑管理 → 编译器管理。七、工具链要求Rust 1.957.1 原因Linux 7.0 将 Rust 工具链要求提升到1.95原因是有三个 unstable 特性在该版本才稳定特性用途offset_of计算 C 结构体字段偏移量用于 unsafe 代码unsafe_fieldsattributes放宽unsafe字段使用场景unsafe extern语法改进FFI 边界更安全7.2 对发行版的影响CI 必须升级 Rust否则 7.0 内核编译不通过发行版提前适配Fedora、Debian、Arch 等需同步升级工具链八、案例8.1 Nova GPU 驱动定位NVIDIA GPU 的纯 Rust 驱动意义Rust for Linux 的样板工程性能热路径性能与等价 C 实现差距在1% 以内8.2 Synology NAS 驱动定位首个非实验性的商用 Rust 代码意义商业公司愿意在生产环境使用里程碑证明 Rust 在企业级场景可用8.3 Asahi Linux GPU 驱动定位Apple Silicon 显卡的 Rust 实现意义最早的压力测试用户之一8.4为什么从驱动切入选择 Driver Core 作为 Rust “毕业” 的第一个子系统原因有三API 最稳定20 年没大改适合做 binding驱动数量最多能最快验证 Rust 生态漏洞重灾区内核 C 代码一半安全漏洞来自驱动堵口投入产出比最高九、价值总结9.1 范式转变这场改造的本质不是把 C 翻译成 Rust而是把内存安全这件事从程序员的纪律上升为**编译器的保证**。过去 30 年Rust 引入后靠文档规范靠类型系统靠 code review靠编译器检查靠静态分析工具靠 RAII 生命周期人类记忆易出错编译器零失误9.2 责任边界设计Greg KH 明天想删 struct device 一个字段 ↓ 他不需要问任何 Rust 维护者 ↓ 他动 C 代码 → 编译错误自然落在 Rust 侧 ↓ 由 Rust 维护者去修 binding ↓ C 不用迁就 RustRust 也不能绑架 C这是 Linus 定下的铁律也是改造能落地的核心前提。9.3 性能代价失去的一些unsafe代码块最小必要使用得到的整类 bug 在编译期消失内存泄露、use-after-free、资源泄露实测数据Nova 驱动热路径性能差距 1%十、展望Driver Core 只是第一个毕业的系统。接下来网络子系统文件系统调度器都会逐步长出 Rust 抽象层。历史意义这是 Linux 内核史上第一次迎接一门新的系统语言。6 年前 Miguel Ojeda 发第一封 RFC 时没几个人相信能成今天 Rust 已正式成为内核一等公民。