使用 WSL2 和 QEMU 体验 Rust for Linux:从零写出并运行自己的模块
AIGC 声明:【chip-tip计划】 这是一个使用 AIGC 帮助收集工作生活中经验的计划。本文内容由我实践得出, 请 AI 润色,如有疏漏,敬请提出。
这篇文章的目标很具体:在一台 Windows 电脑上,使用 WSL2 编译一份带 Rust 支持的 Linux
内核,在 QEMU 里启动它,然后让你写的 Rust 模块真的运行起来。
走完主线,你会得到三种相同结果的办法:
- 把模块编进内核(
CONFIG_SAMPLE_RUST_HELLO=y),启动时自动运行; - 把模块编成可加载文件(
hello_rust.ko),在运行中的 guest 里用
insmod和rmmod管理; - 不修改内核源码,把模块放在树外,用 Kbuild 的
M=机制编译后加载。
这里的 guest 是 QEMU 里的 Linux,host 是 WSL2。三种方式使用同一份内核和同一套工具链,
区别只在模块被放进内核的时机。
文章按“先得到现象,再解释原因”的顺序写。命令可以整段复制。
每一步都有验收条件;没有看到验收结果,就先不要进入下一步。
先建立一个正确的 R4L 心智模型
Rust for Linux(下文简称 R4L)不是把整个 Linux 内核翻译成 Rust,也不是给 C 代码
套一层语法糖。它解决的是一个更窄、更实际的问题:
把内核中容易靠约定出错的规则,尽量变成 Rust 类型和编译器能够检查的规则,
同时保留 Linux 已经拥有的调度、内存、设备和驱动模型。
三层边界
把一段 Rust 驱动放进 Linux 后,代码大致经过下面三层:
你的驱动 / 模块
│ 使用 Rust 类型、借用、Result 和内核抽象
▼
rust/kernel/ ← 面向驱动的 Rust API
│ 少量、集中、经过论证的 unsafe
▼
rust/bindings/ + rust/helpers/ ← bindgen 生成的 C 声明和辅助函数
│
▼
C 内核、头文件和硬件
rust/bindings 是 C 接口的机械映射。它并不会自动变安全。直接调用
bindings::some_c_function(),本质上等于在 Rust 里写了一次没有类型保护的 C
调用,只是还必须放进 unsafe 块。
R4L 的约定因此很重要:
- 叶子模块(驱动、样例、你的实验代码)优先调用
rust/kernel/的抽象; - 需要接触 C 指针、寄存器或不变量时,把
unsafe集中在抽象层; - 每个
unsafe都要写清楚前置条件(通常是// SAFETY: ...); - 驱动作者得到的是“能被类型系统检查的接口”,而不是一堆散落在业务代码里的 C 细节。
可以用锁来理解这种设计。C 代码通常把锁和数据分成两个变量:
static DEFINE_MUTEX(lock);
static struct config cfg;
mutex_lock(&lock);
cfg.timeout = 5;
mutex_unlock(&lock);
“访问 cfg 之前必须先持有 lock”是约定,编译器看不见。
Rust 抽象把数据放进锁里:
let mut guard = config.lock();
guard.timeout = 5;
// guard 离开作用域时自动解锁
没有拿到 guard,就拿不到受保护的数据;guard 的生命周期不能超过锁;离开作用域时
Drop 自动解锁。底层调用 C 锁函数的 unsafe 仍然存在,只是被收敛
在少数经过审查的地方。
这就是 R4L 的核心哲学:不是假装内核没有不安全操作,而是把不安全操作放在可审计的
边界内,让调用者使用更强的类型契约。
一个模块由什么组成
最小的 Rust 内核模块通常包含三块:
use kernel::prelude::*;
module! {
type: HelloRust,
name: "hello_rust",
authors: ["R4L learner"],
description: "A first Rust module",
license: "GPL",
}
struct HelloRust;
impl kernel::Module for HelloRust {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("模块初始化成功\n");
Ok(HelloRust)
}
}
impl Drop for HelloRust {
fn drop(&mut self) {
pr_info!("模块正在卸载\n");
}
}
module!生成模块元数据、参数和初始化入口;Module::init是模块被加载时运行的路径,返回Ok(Self)才表示初始化
成功;Drop是可卸载模块的清理路径;- 没有
std,也没有用户态的进程库。你使用的是内核提供的
kernelcrate。
CONFIG_...=y 和 CONFIG_...=m 会改变生命周期:
| 配置 | 产物 | 什么时候运行 | 能否卸载 |
|---|---|---|---|
y |
链进vmlinux / bzImage |
内核启动阶段 | 不能 |
m |
独立的.ko |
guest 里执行insmod 时 |
可以,前提是打开卸载支持 |
0. 你要准备什么
本文按下面的环境写:
- Windows 10 2004 及以上,或 Windows 11;
- BIOS/UEFI 已开启硬件虚拟化;
- WSL2 Ubuntu 22.04;
- 至少 30 GB 可用磁盘,8 GB 内存,4 个 CPU;
- 能访问 kernel.org、apt.llvm.org 和 crates.io。
把内核源码放在 WSL 的 Linux 文件系统中,例如 ~/workspace,不要放在
/mnt/c 下。跨 Windows 挂载点编译会明显变慢,还可能遇到权限和大小写问题。
全文会出现三种提示符:
| 提示符 | 位置 | 用途 |
|---|---|---|
PS> |
Windows 管理员 PowerShell | 安装 WSL |
| ` | 提示符 | 位置 |
| ------- | ------------------------- | ---------------------------- |
PS> |
Windows 管理员 PowerShell | 安装 WSL |
| WSL Ubuntu | 编译、制作 rootfs、启动 QEMU | |
/ # |
QEMU guest | 挂载 9p、加载和卸载模块 |
命令前的提示符不是命令本身。复制代码块时,只复制提示符后面的内容。
1. 安装 WSL2 和 Ubuntu
在 Windows 中打开“管理员 PowerShell”:
wsl --install -d Ubuntu-22.04
wsl --update
如果系统提示重启,就重启。第一次进入 Ubuntu 时,创建一个 Linux 用户名和密码;这个
密码以后给 sudo 使用。
进入 Ubuntu 后检查磁盘:
df -h /
mkdir -p ~/workspace
cd ~/workspace
/ 的可用空间最好不少于 30 GB。
2. 下载内核树
仍在 WSL 中执行:
git clone --depth=1 \
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git \
~/workspace/linux
cd ~/workspace/linux
git describe --always --dirty
test -f scripts/min-tool-version.sh
最后一个 test 没有输出且退出码为 0,就说明内核树已经就位。内核是持续开发
的,本文不把某个 commit 当成永久 API;真正的样例和最低版本,以你下载的树为准。
3. 安装工具链
3.1 通用依赖
sudo apt update
sudo apt install -y \
bc bison flex build-essential \
libssl-dev libelf-dev libdw-dev libncurses-dev \
dwarves cpio gzip git curl wget ca-certificates \
python3 pkg-config qemu-system-x86 busybox-static
3.2 LLVM/Clang
本文选择 LLVM 18,并在所有 Kbuild 命令中明确写 LLVM=-18,避免系统里另一个
旧版 clang 被误选中。
cd ~
wget -q https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 18
sudo apt install -y clang-18 lld-18 libclang-18-dev
clang-18 --version | head -1
ld.lld-18 --version | head -1
内核树中的最低版本脚本是最终判据:
cd ~/workspace/linux
printf "llvm: "; ./scripts/min-tool-version.sh llvm
printf "rustc: "; ./scripts/min-tool-version.sh rustc
printf "bindgen: "; ./scripts/min-tool-version.sh bindgen
如果未来的内核把 LLVM 最低版本抬到 19,把上面的 18 换成你安装的版本,并
同步修改后文所有 LLVM=-18。
3.3 Rust、rust-src 和 bindgen
使用 rustup,避免 Ubuntu 自带的旧版 Rust 混进来:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain stable
. "$HOME/.cargo/env"
rustup component add rust-src rustfmt
# 当前内核树的最低要求是 bindgen 0.71.1;若脚本显示更高版本,按它修改这里。
cargo install --locked bindgen-cli --version 0.71.1
cd ~/workspace/linux
rustup override set stable
rust-src 不是可选项。内核需要交叉编译 Rust 的 core,所以要用到标准库
源码。bindgen 则在构建时读取 C 头文件并生成 Rust 绑定。
检查命令是否来自 rustup:
which -a rustc cargo bindgen
rustc -V
bindgen --version
rustup show active-toolchain
第一行中,$HOME/.cargo/bin 应该排在 /usr/bin 前面。如果 bindgen 报找不到
libclang,先定位动态库:
find /usr/lib -name 'libclang-18.so*' -print
如果输出位于 /usr/lib/x86_64-linux-gnu,执行:
export LIBCLANG_PATH=/usr/lib/x86_64-linux-gnu
3.4 内核自检
cd ~/workspace/linux
make LLVM=-18 rustavailable
必须看到:
Rust is available!
如果没有这行,先处理报错,不要继续配内核。常见原因是当前 shell 没加载 rustup:
. "$HOME/.cargo/env"
export PATH="$HOME/.cargo/bin:$PATH"
make LLVM=-18 rustavailable
4. 配置能运行 Rust 的内核
先生成 x86-64 默认配置,再打开实验所需选项:
cd ~/workspace/linux
make LLVM=-18 x86_64_defconfig
./scripts/config --enable RUST
./scripts/config --enable SAMPLES
./scripts/config --enable SAMPLES_RUST
# initramfs、.ko 和 9p 共享目录
./scripts/config --enable BLK_DEV_INITRD
./scripts/config --enable RD_GZIP
./scripts/config --enable MODULES
./scripts/config --enable MODULE_UNLOAD
./scripts/config --enable NET_9P
./scripts/config --enable NET_9P_VIRTIO
./scripts/config --enable 9P_FS
make LLVM=-18 olddefconfig
scripts/config 是内核树里的脚本,必须在内核树根目录执行。olddefconfig 会重新
解析 Kconfig 依赖;脚本成功返回不代表选项最终保留。
grep -E '^(CONFIG_(RUST|SAMPLES|SAMPLES_RUST|BLK_DEV_INITRD|RD_GZIP|MODULES|MODULE_UNLOAD|NET_9P|NET_9P_VIRTIO|9P_FS))=' .config
至少应包含:
CONFIG_RUST=y
CONFIG_SAMPLES=y
CONFIG_SAMPLES_RUST=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_RD_GZIP=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_9P_FS=y
y 表示编进内核,m 表示编成模块。RUST 是 bool 选项,不能设成
m;只有声明为 tristate 的驱动选项才能在 y 与 m 之间切换。
5. 写一个树内 Rust 模块
树内模块放在内核的 samples/rust/,这样它能使用内核已经准备好的
kernel crate 和 Kbuild 规则。
5.1 Rust 源码
创建 samples/rust/hello_rust.rs:
cd ~/workspace/linux
cat > samples/rust/hello_rust.rs <<'RS'
// SPDX-License-Identifier: GPL-2.0
//! My first Rust module.
use kernel::prelude::*;
module! {
type: HelloRust,
name: "hello_rust",
authors: ["R4L learner"],
description: "A first Rust for Linux module",
license: "GPL",
params: {
times: i64 {
default: 1,
description: "How many times to print the greeting",
},
},
}
struct HelloRust;
impl kernel::Module for HelloRust {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("进入 Rust 模块,times={}\n", module_parameters::times.value());
Ok(HelloRust)
}
}
impl Drop for HelloRust {
fn drop(&mut self) {
pr_info!("离开 Rust 模块\n");
}
}
RS
pr_info! 的字符串里没有手写 hello_rust:。日志前缀由
module! 的 name 生成;手写一次会得到重复前缀。
module! 中的 params 让内核注册参数。内核启动命令行使用
hello_rust.times=3;编成 .ko 后,insmod 使用裸参数
times=3,后文会验证。
5.2 接入 Kconfig 和 Kbuild
内核不会因为目录里出现一个 .rs 文件就自动编译它。下面的脚本会:
- 在
samples/rust/Kconfig中声明tristate; - 在
samples/rust/Makefile中加入目标; - 支持重复执行,不会插入重复条目。
cd ~/workspace/linux
python3 - <<'PY'
from pathlib import Path
kconfig = Path("samples/rust/Kconfig")
text = kconfig.read_text()
if "config SAMPLE_RUST_HELLO" not in text:
lines = text.splitlines(keepends=True)
position = next(
i for i in range(len(lines) - 1, -1, -1)
if lines[i].lstrip().startswith("endif")
)
block = """config SAMPLE_RUST_HELLO
tristate "My first Rust module"
help
This option builds my first Rust module.
To compile this as a module, choose M here:
the module will be called hello_rust.
If unsure, say N.
"""
lines.insert(position, block)
kconfig.write_text("".join(lines))
makefile = Path("samples/rust/Makefile")
line = "obj-$(CONFIG_SAMPLE_RUST_HELLO)\t+= hello_rust.o\n"
text = makefile.read_text()
if "CONFIG_SAMPLE_RUST_HELLO" not in text:
makefile.write_text(text + line)
PY
这段脚本把 Kconfig 项插入最后一个 endif 之前,也就是
if SAMPLES_RUST 块内。Makefile 里的 Tab 已由 Python 写入,不要手动换成空格。
6. 方式一:编进内核(=y)
6.1 选择 built-in 并编译
cd ~/workspace/linux
./scripts/config --enable SAMPLE_RUST_HELLO
make LLVM=-18 olddefconfig
grep '^CONFIG_SAMPLE_RUST_HELLO=' .config
# 应该是 CONFIG_SAMPLE_RUST_HELLO=y
make LLVM=-18 -j"$(nproc)"
test -x arch/x86/boot/bzImage
ls -lh arch/x86/boot/bzImage vmlinux
第一次编译需要几分钟,之后只改一个 Rust 文件通常是增量编译。=y 模块在内核
启动阶段自动执行,不能在 guest 中卸载。
6.2 制作最小 initramfs
没有 rootfs 时,模块可能已经运行,但内核最后会因找不到 /init 而 panic。
为了让三种方式都能进入 shell,制作一个只有 BusyBox 的 initramfs:
ROOTFS="$HOME/r4l-initramfs"
rm -rf "$ROOTFS"
mkdir -p "$ROOTFS"/{bin,dev,proc,sys,tmp}
cp /usr/bin/busybox "$ROOTFS/bin/busybox"
for applet in sh ls cat echo mount umount dmesg uname mkdir \
poweroff insmod rmmod lsmod; do
ln -sf busybox "$ROOTFS/bin/$applet"
done
cat > "$ROOTFS/init" <<'INIT'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev 2>/dev/null
echo "R4L initramfs is ready"
exec /bin/sh
INIT
chmod 0755 "$ROOTFS/init"
cd "$ROOTFS"
find . -print0 \
| cpio --null -o --format=newc 2>/dev/null \
| gzip -9 > "$HOME/r4l-initramfs.cpio.gz"
ls -lh "$HOME/r4l-initramfs.cpio.gz"
newc 格式和可执行的 0755 权限不能省。QEMU 会把归档交给内核,
内核解开后执行 /init。
6.3 启动 QEMU
cd ~/workspace/linux
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd "$HOME/r4l-initramfs.cpio.gz" \
-append "console=ttyS0 hello_rust.times=3" \
-nographic -no-reboot -m 1024 -smp 2
你应该看到类似:
hello_rust: 进入 Rust 模块,times=3
R4L initramfs is ready
/ #
时间戳和其他内核日志会不同。看到 hello_rust: 这一行,方式一就成功了。
在 guest 中输入 poweroff -f 关机。若 guest 没响应,按
Ctrl-A,再按 X 退出 QEMU。
7. 方式二:编成 .ko,在运行中的 guest 里加载
这种方式适合快速迭代:只重编 .ko,然后在同一个 guest 中
rmmod / insmod,不必重启。
7.1 改为模块
cd ~/workspace/linux
./scripts/config --module SAMPLE_RUST_HELLO
make LLVM=-18 olddefconfig
grep '^CONFIG_SAMPLE_RUST_HELLO=' .config
# 应该是 CONFIG_SAMPLE_RUST_HELLO=m
make LLVM=-18 -j"$(nproc)"
ls -lh samples/rust/hello_rust.ko
这次启动日志里不会自动出现 hello_rust,因为它需要 insmod。
7.2 用 9p 把 .ko 共享给 QEMU
cd ~/workspace/linux
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd "$HOME/r4l-initramfs.cpio.gz" \
-virtfs "local,path=$HOME/workspace/linux/samples/rust,mount_tag=host0,security_model=none" \
-append "console=ttyS0" \
-nographic -no-reboot -m 1024 -smp 2
进入 / # 后执行:
mkdir -p /mnt/host
mount -t 9p -o trans=virtio,version=9p2000.L host0 /mnt/host
insmod /mnt/host/hello_rust.ko times=7
lsmod
rmmod hello_rust
应看到:
hello_rust: 进入 Rust 模块,times=7
hello_rust: 离开 Rust 模块
对 insmod 来说,参数必须写成 times=7。hello_rust.times=7 是
内核启动命令行的写法,不是 insmod 的写法。
7.3 体验增量迭代
保持 QEMU 运行,在另一个 WSL 终端改源码并重编:
cd ~/workspace/linux
sed -i 's/进入 Rust 模块/再次进入 Rust 模块/' samples/rust/hello_rust.rs
make LLVM=-18 -j"$(nproc)"
回到 guest:
rmmod hello_rust
insmod /mnt/host/hello_rust.ko times=11
9p 共享的是目录,新生成的 .ko 会立即可见。若你修改了内核配置、模块依赖或
内核本身,仍然需要重启 QEMU。
8. 方式三:树外模块(不修改内核树)
树内方式适合学习 API 和准备上游补丁。自己的项目也可以放在内核树外:内核树一行不改,
单独目录里放一个 Rust 文件和一个 Kbuild Makefile。
8.1 创建两个文件
mkdir -p "$HOME/r4l-external"
cd "$HOME/r4l-external"
cat > Makefile <<'MAKE'
obj-m := r4l_external.o
MAKE
cat > r4l_external.rs <<'RS'
// SPDX-License-Identifier: GPL-2.0
//! Out-of-tree Rust module.
use kernel::prelude::*;
module! {
type: R4lExternal,
name: "r4l_external",
authors: ["R4L learner"],
description: "An out-of-tree Rust module",
license: "GPL",
}
struct R4lExternal;
impl kernel::Module for R4lExternal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("树外 Rust 模块已加载\n");
Ok(R4lExternal)
}
}
impl Drop for R4lExternal {
fn drop(&mut self) {
pr_info!("树外 Rust 模块已卸载\n");
}
}
RS
obj-m := r4l_external.o 的左边决定最终模块身份,因此它必须和
r4l_external.rs 同名(只把 .rs 换成 .o)。树外模块不需要
Kconfig:执行 make 就表示“我要编译它”。
8.2 使用内核 Kbuild 编译
树外目录不在内核树的子目录里,因此单独设置 rustup override:
cd "$HOME/r4l-external"
rustup override set stable
make -C "$HOME/workspace/linux" M="$PWD" LLVM=-18
ls -lh r4l_external.ko
这条命令的含义:
-C:进入已经配置和准备好的内核树;M=$PWD:告诉 Kbuild 这个目录包含外部模块;LLVM=-18:保证模块和内核使用相同的 LLVM 工具链。
不要直接运行 rustc r4l_external.rs。模块还需要内核头文件、内核生成的
kernel crate、编译选项、符号版本和 vermagic,只有 Kbuild 能把这些组合起来。
8.3 在 QEMU 中加载树外模块
关掉方式二的 QEMU,再用同一份内核和 initramfs 启动,换共享目录:
cd ~/workspace/linux
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd "$HOME/r4l-initramfs.cpio.gz" \
-virtfs "local,path=$HOME/r4l-external,mount_tag=host0,security_model=none" \
-append "console=ttyS0" \
-nographic -no-reboot -m 1024 -smp 2
guest 中:
mkdir -p /mnt/host
mount -t 9p -o trans=virtio,version=9p2000.L host0 /mnt/host
insmod /mnt/host/r4l_external.ko
rmmod r4l_external
应看到:
r4l_external: 树外 Rust 模块已加载
r4l_external: 树外 Rust 模块已卸载
三种方式的差异可以这样记:
| 方式 | 源码位置 | 配置/构建 | 运行方式 |
|---|---|---|---|
| 树内 built-in | samples/rust/ |
Kconfig 设y |
启动时自动运行 |
| 树内模块 | samples/rust/ |
Kconfig 设m |
guest 中insmod |
| 树外模块 | 独立目录 | make -C ... M=... |
guest 中insmod |
9. 为什么这些步骤体现了 R4L 哲学
9.1 不要在驱动里扩散 bindings
你可以写:
unsafe {
bindings::some_c_api(...);
}
但你还需要自己证明指针有效、对象已初始化、当前上下文可以睡眠、生命周期正确、并发
访问受保护。Rust 编译器无法从一条原始 C 声明推导这些规则。
R4L 的做法是把这些证明放进抽象层,模块只使用
Mutex<T>、设备、文件、DMA 等带契约的类型。若抽象层没有覆盖某个能力,正确
方向是先理解不变量,再给抽象层补 API,而不是在每个驱动中复制 C 绑定。
9.2 为什么 init 返回对象
Module::init 返回 Result<Self>。成功时这个 Self 会由内核持有;
初始化失败可以返回 Err;成功后的资源清理由 Drop 负责。
这不是把 C 的函数名换成 Rust 的函数名,而是把资源所有权放进对象生命周期。Rust 仍然
不会替你修复错误的硬件协议或错误的并发设计,但它会拒绝许多悬垂引用、重复释放和
跨线程移动不允许移动的值。
9.3 推荐阅读顺序
cd ~/workspace/linux
less samples/rust/rust_minimal.rs
less rust/kernel/sync/lock.rs
less Documentation/rust/quick-start.rst
less Documentation/kbuild/modules.rst
先看样例怎样声明模块,再看一个小型抽象如何把 C 不变量封装起来,最后进入设备驱动、
异步上下文、DMA 和锁的组合。
网络版的上游入口是 Rust Quick Start 和
Building External Modules;版本不一致时,
以你手上内核树的 Documentation/ 和 samples/rust/ 为准。
10. 排错表
工具链
make rustavailable没有Rust is available!:执行
. "$HOME/.cargo/env",确认which rustc指向
$HOME/.cargo/bin/rustc。- 内核编译成功但
CONFIG_RUST没有:编译成功不代表 Rust 被启用;先修复
rustavailable,再重新执行配置。 - bindgen 报找不到 libclang:安装
libclang-18-dev,将
LIBCLANG_PATH设置为实际包含libclang-18.so的目录。 - 树外报
unknown codegen option: jump-tables:在树外目录执行
rustup override set stable,比较内核树和模块目录中的rustc -V。
Kconfig 和源码
SAMPLE_RUST_HELLO在olddefconfig后消失:确认
SAMPLES=y、SAMPLES_RUST=y,并且 Kconfig 项位于
if SAMPLES_RUST块内。./scripts/config: No such file:你不在内核树根目录。No rule to make target:检查obj-m/obj-y的目标名是否与
.rs文件名一致。- 改源码但日志没变:
=y要重编内核,=m要重编
.ko,然后在 guest 中重新加载。
QEMU 和模块
- QEMU 黑屏:x86 使用
console=ttyS0,并保留-nographic。 - 反复重启:加
-no-reboot。 VFS: Unable to mount root fs:没有传入 initramfs,或归档中的
/init不存在、不可执行、格式不是newc。mount -t 9p失败:确认NET_9P、
NET_9P_VIRTIO、9P_FS都是y,且
host0与mount_tag=host0完全一致。insmod: Invalid module format:模块必须使用正在运行的同一份
.config、同一棵内核树和同一套工具链编译。rmmod找不到模块:使用 Kbuild 目标名,例如
r4l_external,不要只看module!的显示名。- 参数没有生效:内核启动命令行使用
hello_rust.times=3;
insmod使用times=3。
11. 接下来怎么学
你已经完成了完整闭环:
写 Rust
→ module! 注册生命周期和参数
→ Kconfig/Kbuild 决定构建形态
→ LLVM + rustc + bindgen 生成内核产物
→ QEMU 启动同一份 bzImage
→ 串口日志证明模块执行
下一步可以:
- 把
HelloRust的状态放进Mutex<T>,练习 guard 和生命周期; - 追踪
rust/kernel/中一个抽象,找到它内部唯一的unsafe调用; - 把树外模块改成 misc device,再逐步加入错误处理、并发和资源释放。
每次写 Rust 内核代码,都先问:“这个不变量在哪里被表达和检查?”如果答案只是“我在
注释里记得了”,通常说明抽象边界还没有设计好。

浙公网安备 33010602011771号