ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用Rust编写Linux内核文件系统模块:从原理到实践

2026/8/22 20:30:16 拓冰建站 浏览量
用Rust编写Linux内核文件系统模块:从原理到实践 这次我们来看一个在 Linux 内核开发领域颇具探索性的项目用几乎纯 Rust 语言编写一个 Linux 文件系统模块。这并非一个简单的用户态程序而是直接与内核交互、管理磁盘数据结构的底层驱动。对于关注系统编程、Rust 语言安全性以及 Linux 内核未来发展的开发者来说这是一个极具吸引力的技术实践。项目的核心价值在于它挑战了传统内核开发尤其是 C 语言的固有模式试图利用 Rust 的内存安全、无数据竞争等特性来构建更可靠、更少潜在漏洞的内核模块。本文将带你了解这一尝试的核心能力、环境门槛、构建流程并通过一个简单的示例演示如何从零开始搭建一个基础的、可加载的 Rust 文件系统模块框架。无论你是想深入学习 Rust 系统编程还是对 Linux 内核模块化开发感兴趣这篇文章都将提供一条清晰的实践路径。1. 核心能力速览能力项说明项目类型Linux 内核文件系统模块内核对象.ko 文件主要语言Rust“几乎纯 Rust”核心逻辑由 Rust 实现内核接口通过bindgen生成的 Rust FFI 绑定调用 Linux Kernel API核心目标探索 Rust 用于内核开发的可行性构建一个具备基本mount、read、write等操作的文件系统原型构建系统基于cargo和自定义的Makefile与内核kbuild系统集成硬件/环境门槛需要完整的 Linux 内核源码、对应版本的 Rust 工具链含rust-src组件和编译环境运行要求需在支持模块加载的 Linux 系统上以root或sudo权限进行测试适合场景内核与系统编程学习、Rust 语言高级特性实践、新型文件系统原型验证、安全性研究2. 适用场景与使用边界这个项目主要适用于以下几类开发者系统编程学习者希望超越应用层理解操作系统如何管理文件和存储。Rust 进阶开发者渴望将 Rust 的安全特性应用于最底层的系统软件挑战no_std环境和与 C 代码的交互。内核研究员/爱好者对 Linux 内核模块开发、VFS虚拟文件系统层工作原理有浓厚兴趣。新型文件系统原型设计者需要一种更安全、更现代的语言来快速实现和验证新的文件系统设计理念。使用边界与重要提醒非生产级工具这通常是一个实验性或教育性的项目其稳定性、性能和完整性远未达到生产级别如 ext4, Btrfs。切勿将其用于存储任何重要数据。高风险操作内核模块运行在特权级别Ring 0一个有缺陷的模块可能导致系统崩溃Kernel Panic、数据损坏或安全漏洞。务必在虚拟机或专属的测试机器上进行实验。知识门槛需要同时具备 Rust 语言特别是unsafe、FFI、no_std和 Linux 内核编程的基础知识。法律与合规项目代码需遵循其所使用的内核头文件对应的开源协议如 GPL。在借鉴或修改任何内核代码时必须严格遵守相关许可证条款。3. 环境准备与前置条件在开始编码之前必须准备好一个能够编译 Linux 内核模块的完整环境。操作系统: 推荐使用主流的 Linux 发行版如 Ubuntu 22.04 LTS 或 Fedora 38。需要在物理机或虚拟机中操作。必备软件包:Rust 工具链: 需要安装rustc,cargo以及rust-src组件用于标准库源码。curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env rustup component add rust-src内核头文件与构建工具: 安装当前运行内核对应的开发包和构建工具链。# Ubuntu/Debian sudo apt update sudo apt install build-essential libssl-dev flex bison libelf-dev sudo apt install linux-headers-$(uname -r) # Fedora/RHEL/CentOS sudo dnf install kernel-devel gcc make elfutils-libelf-devel openssl-develbindgen依赖:bindgen用于从 C 头文件生成 Rust 绑定需要clang。# Ubuntu/Debian sudo apt install clang libclang-dev # Fedora sudo dnf install clang clang-develLinux 内核源码: 你需要获取与你当前运行内核版本相匹配的完整内核源码。有两种方式从发行版仓库安装源码包如linux-source-$(uname -r)。从 kernel.org 下载稳定版源码并解压。建议使用第一种方式兼容性更好。获取后记下源码路径例如/usr/src/linux-headers-$(uname -r)。项目初始化: 创建一个新的 Rust 库项目但需要特殊配置。cargo new --lib my_rust_fs cd my_rust_fs接下来需要配置Cargo.toml和创建必要的构建脚本。4. 项目结构与关键配置一个 Rust 内核模块的项目结构与普通 Rust 项目差异很大。Cargo.toml配置:[package] name my_rust_fs version 0.1.0 edition 2021 # 这是一个 staticlib将被链接进内核模块 [lib] crate-type [staticlib] # 非常重要告诉编译器我们不需要标准库使用 core 和 alloc panic abort # 依赖项 [dependencies] # 可能需要的辅助库例如用于打印的 kernel crate如果存在 # 目前更多是直接绑定内核API这里可能为空或包含一些工具库 [build-dependencies] # 用于构建时生成绑定代码 bindgen 0.66构建脚本build.rs: 这是项目的核心之一负责调用bindgen生成内核 API 的 Rust 绑定。// build.rs use std::env; use std::path::PathBuf; fn main() { // 告诉 cargo 如果内核头文件变化需要重新运行 build.rs println!(cargo:rerun-if-changedwrapper.h); // 设置 bindgen 参数 let bindings bindgen::Builder::default() .header(wrapper.h) // 我们自定义的包装头文件 .parse_callbacks(Box::new(bindgen::CargoCallbacks)) // 添加内核编译时需要的宏定义和包含路径 .clang_arg(-I/usr/src/linux-headers-$(uname -r)/include) .clang_arg(-D__KERNEL__) .clang_arg(-D__BINDGEN__) // 可能需要的自定义宏 .generate() .expect(Unable to generate bindings); // 输出绑定文件到 OUT_DIR let out_path PathBuf::from(env::var(OUT_DIR).unwrap()); bindings .write_to_file(out_path.join(bindings.rs)) .expect(Couldnt write bindings!); }包装头文件wrapper.h: 这个文件#include所有你需要用到的内核头文件。// wrapper.h #ifndef WRAPPER_H #define WRAPPER_H #include linux/fs.h // 文件系统相关结构inode, file_operations #include linux/module.h // 模块加载卸载宏 #include linux/kernel.h // printk 等 #include linux/slab.h // 内核内存分配 // ... 根据你的文件系统需要添加其他头文件 #endif内核模块的Makefile: Rust 代码编译成静态库后需要通过一个传统的Makefile调用内核的kbuild系统将其链接成.ko文件。# 指向你的内核源码目录 KERNEL_DIR ? /lib/modules/$(shell uname -r)/build # 你的 Rust 静态库路径 RUST_TARGET target/x86_64-unknown-none/release/libmy_rust_fs.a # 模块最终名称 obj-m myrustfs.o # 告诉 kbuild这个模块由两部分链接而成一个自动生成的C包装器 Rust静态库 myrustfs-y : rust_wrapper.o $(RUST_TARGET) # 构建目标 all: # 首先用 cargo 编译 Rust 代码目标为自定义的 no_std 目标 cargo build --release --target x86_64-unknown-none # 然后编译C包装器并链接成内核模块 $(MAKE) -C $(KERNEL_DIR) M$(PWD) modules clean: $(MAKE) -C $(KERNEL_DIR) M$(PWD) clean cargo clean .PHONY: all clean5. Rust 文件系统模块核心实现现在进入最关键的 Rust 代码部分。我们需要在src/lib.rs中实现文件系统的基本骨架。第一步设置 crate 属性与导入// src/lib.rs #![no_std] // 没有标准库 #![feature(allocator_api)] // 可能需要用于全局分配器 #![feature(lang_items)] // 定义语言项如 panic 处理 // 引入 core 和 alloc extern crate alloc; use alloc::string::String; use core::panic::PanicInfo; // 包含由 build.rs 生成的绑定 include!(concat!(env!(OUT_DIR), /bindings.rs)); // 定义全局分配器。在内核中我们需要将内存分配导向 kmalloc/kfree。 // 这是一个简化示例实际需要实现 GlobalAlloc trait。 #[global_allocator] static ALLOCATOR: KernelAllocator KernelAllocator; struct KernelAllocator; unsafe impl alloc::alloc::GlobalAlloc for KernelAllocator { unsafe fn alloc(self, layout: core::alloc::Layout) - *mut u8 { // 这里应该调用 bindings 中生成的 kmalloc 函数 // 为简化返回空指针。实际项目需要完整实现。 core::ptr::null_mut() } unsafe fn dealloc(self, _ptr: *mut u8, _layout: core::alloc::Layout) { // 调用 kfree } } // Panic 处理函数打印信息并挂起或退出 #[panic_handler] fn panic(_info: PanicInfo) - ! { // 调用内核的打印函数例如 printk // unsafe { bindings::printk(bRust FS Panic!\n\0.as_ptr() as *const i8) }; loop {} // 或调用 bindings::BUG() }第二步定义文件操作结构这是文件系统的“心脏”它是一组函数指针定义了当用户态程序执行open,read,write等操作时内核应该调用我们的哪些 Rust 函数。// 为 file_operations 实现一个包装器。这是一个巨大的 unsafe 块因为我们在与C接口交互。 use core::mem::MaybeUninit; static RUSTFS_FILE_OPS: bindings::file_operations unsafe { let mut ops MaybeUninit::bindings::file_operations::zeroed().assume_init(); // 初始化结构体字段 // 注意这里所有字段都必须赋值否则是未定义行为。 ops.owner core::ptr::null_mut(); // 通常设为 THIS_MODULE ops.llseek None; ops.read Some(rustfs_read); // 指向我们的 Rust 函数 ops.write Some(rustfs_write); ops.open Some(rustfs_open); ops.release Some(rustfs_release); // ... 初始化其他需要的操作不需要的设为 None ops }; // 接下来是各个操作函数的 Rust 实现。它们必须符合C ABI。 extern C fn rustfs_open( _inode: *mut bindings::inode, _file: *mut bindings::file, ) - core::ffi::c_int { // 打开文件时的操作返回 0 表示成功负数表示错误码 0 } extern C fn rustfs_read( _file: *mut bindings::file, buf: *mut core::ffi::c_char, count: bindings::size_t, _offset: *mut bindings::loff_t, ) - bindings::ssize_t { // 模拟读取操作向用户缓冲区写入一些数据 let data bHello from Rust FS!\n; let len data.len().min(count as usize); unsafe { core::ptr::copy_nonoverlapping(data.as_ptr(), buf as *mut u8, len); } len as bindings::ssize_t } extern C fn rustfs_write( _file: *mut bindings::file, _buf: *const core::ffi::c_char, _count: bindings::size_t, _offset: *mut bindings::loff_t, ) - bindings::ssize_t { // 写入操作这里简单返回写入的字节数模拟成功 _count as bindings::ssize_t } extern “C” fn rustfs_release( _inode: *mut bindings::inode, _file: *mut bindings::file, ) - core::ffi::c_int { // 释放文件时的清理操作 0 }第三步模块初始化与退出我们需要提供模块被insmod加载和rmmod卸载时调用的函数。// 模块初始化函数 #[no_mangle] pub extern C fn init_module() - core::ffi::c_int { // 这里应该进行 // 1. 注册文件系统类型 (register_filesystem) // 2. 创建测试用的超级块和根目录 inode // 为了示例简化我们仅打印一条日志。 unsafe { // 调用内核的 printk。注意字符串需要以空字符结尾。 let msg bRust Filesystem module loaded successfully!\n\0; bindings::printk(msg.as_ptr() as *const i8); } 0 // 返回 0 表示成功 } // 模块清理函数 #[no_mangle] pub extern C fn cleanup_module() { // 这里应该进行 // 1. 卸载文件系统 (unregister_filesystem) // 2. 释放所有分配的资源 unsafe { let msg bRust Filesystem module unloaded.\n\0; bindings::printk(msg.as_ptr() as *const i8); } }6. C 语言包装器与最终链接Rust 编译器会生成一个静态库.a文件。但内核的模块加载器期望一个遵循 Linux 内核模块约定的初始化函数。因此我们需要一个极薄的 C 语言包装器来充当“粘合剂”。rust_wrapper.c:#include linux/init.h #include linux/module.h // 声明由 Rust 代码提供的两个函数 extern int init_module(void); extern void cleanup_module(void); // 将模块信息许可证、作者等放在C这边是常见做法 MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(A simple filesystem module written in Rust); MODULE_VERSION(0.1); // 这个文件本身不做什么只是让 kbuild 能找到符号并链接 Rust 库。 // 实际的 init_module 和 cleanup_module 由 Rust 实现。这个 C 文件会被编译成rust_wrapper.o然后与libmy_rust_fs.a一起被链接到最终的myrustfs.ko中这个过程由我们之前编写的Makefile控制。7. 编译、加载与测试流程现在我们可以尝试完整的构建和测试循环。第一步编译模块在项目根目录执行make如果一切顺利你将看到cargo编译 Rust 代码然后make调用内核构建系统最终生成myrustfs.ko文件。第二步加载模块使用insmod命令以 root 权限加载模块sudo insmod myrustfs.ko加载后立即使用dmesg查看内核日志应该能看到我们init_module函数中打印的 “Rust Filesystem module loaded successfully!” 信息。sudo dmesg | tail -5第三步验证模块状态使用lsmod检查模块是否在已加载模块列表中lsmod | grep myrustfs第四步卸载模块使用rmmod命令卸载模块sudo rmmod myrustfs再次查看dmesg应该能看到卸载时打印的信息。8. 功能验证与下一步探索目前我们的模块只是一个“空壳”它虽然能加载和卸载并响应简单的read操作返回固定字符串但还不是一个真正的、可挂载的文件系统。要成为一个真正的文件系统你需要深入实现以下内容定义file_system_type结构体并在init_module中调用register_filesystem进行注册。实现super_operations包括alloc_inode,destroy_inode,statfs等。实现inode_operations和file_operations我们已经有了一个简单的file_operations但需要更完整的函数集并实现inode_operations来管理文件属性、目录项等。磁盘数据结构定义你的文件系统在磁盘上的布局超级块、inode 表、数据块位图等并实现相应的读写解析逻辑。内存管理完善全局分配器正确地将 Rust 的alloc请求转发给内核的kmalloc/kfree并处理好内存释放。一个更进阶的测试是实现一个基于内存ramfs的简单文件系统。这样无需处理真实的块设备可以专注于 VFS 接口的对接。你可以创建一个目录然后通过mount -t myrustfs none /mnt/mypoint来挂载它验证基本的文件创建、读写和删除功能。9. 常见问题与排查方法在开发过程中你几乎一定会遇到各种编译和运行时错误。问题现象可能原因排查方式解决方案cargo build失败提示cant find crate for coreRust 工具链未正确配置no_std环境或目标平台不对。检查Cargo.toml的crate-type和#![no_std]属性。确认安装了rust-src。确保使用--target x86_64-unknown-none进行编译。创建一个.cargo/config.toml文件来设置默认目标。make失败内核构建系统报错提示找不到头文件内核头文件路径不正确或版本不匹配。检查KERNEL_DIR变量和build.rs中的-I参数路径。确认$(uname -r)与已安装的头文件版本一致。使用绝对路径。通过 apt list --installedinsmod失败报Invalid module format模块与当前运行内核的版本不兼容vermagic 不匹配。使用modinfo myrustfs.ko查看模块的 vermagic 字符串。确保编译模块的内核源码版本与uname -r完全一致。在Makefile的make命令中指定KERNEL_DIR为正确的路径。模块加载后系统崩溃Kernel PanicRust 代码中存在未定义行为如空指针解引用、错误的unsafe块或与内核的约定不符。分析dmesg输出的崩溃调用栈Oops。在虚拟机中测试以避免真机损坏。极度谨慎地使用unsafe。确保所有从 C 端传入的指针在解引用前都经过有效性检查。使用bindgen生成的类型可能包含生命周期信息需正确处理。编译时bindgen生成大量错误内核头文件包含了许多 Rust 不兼容的语法或宏。查看build.rs输出的具体错误。在wrapper.h中精确定义需要绑定的头文件和函数避免包含不必要的头文件。使用bindgen的blocklist功能过滤不需要的类型。链接阶段失败提示未定义引用Rust 静态库中的符号名与 C 包装器期望的不匹配。使用nm工具查看libmy_rust_fs.a和rust_wrapper.o中的符号。确保 Rust 中的#[no_mangle] pub extern C函数名与 C 中extern声明的完全一致。检查Makefile中链接对象的顺序。10. 最佳实践与开发建议从小开始逐步验证不要一开始就试图实现完整的 ext4。从一个除了加载卸载什么都不做的模块开始然后逐步添加printk日志、简单的read函数、file_operations结构最后再挑战super_operations和磁盘交互。充分利用 Rust 的安全抽象即使在内核中也应尽可能将unsafe代码封装在安全的抽象层后。例如为*mut bindings::inode创建一个安全的包装类型提供方法而不是直接操作指针。密集的日志输出在内核开发中printk是你的好朋友。在每一个关键步骤函数入口、错误处理、数据转换处都添加日志这在调试时至关重要。版本控制与备份内核模块开发极易导致系统不稳定。务必在虚拟机中进行并频繁使用快照功能。代码使用 Git 管理。深入学习内核知识编写文件系统模块要求你对 Linux VFS 层有扎实的理解。推荐阅读《Linux Kernel Development》和《The Linux Programming Interface》相关章节。参考现有项目关注 Rust for Linux 内核的官方进展 ( https://github.com/Rust-for-Linux )。虽然它主要关注将 Rust 支持并入主线内核但其代码是极佳的学习资源。也有一些实验性的用户态文件系统FUSE用 Rust 编写可以作为学习 VFS 接口的参考。用 Rust 编写 Linux 内核模块尤其是文件系统这样的核心组件是一条充满挑战但回报丰厚的路径。它迫使你同时深入理解操作系统原理和一门现代系统级编程语言的最晦涩特性。成功加载第一个自制的 Rust 模块并看到它在内核日志中打印信息所带来的成就感是无可比拟的。这个项目目前更像一个“技术探针”但它清晰地指向了一个未来用更安全的语言构建更可靠的基础软件。建议从文中的简化框架入手先打通编译加载的整个工具链再选择一个简单的功能点比如实现一个/proc下的只读文件进行突破逐步积累经验和信心。