#rust# #kernel# #内核#
今天就正式进入到 Rust for Linux 的内容,经过之前的内容,已经构建出最小化的内核。不过有点点的小毛病,先不管那些小毛病。看看怎么和 Rust for linux 工作。
代码补全
内核项目内部
我选择的是 neovim
+coc-rust-analyzer
。进入到 rust_out_of_tree
目录,编辑 rust_out_of_tree.rs
文件。发现是没有补全的,这个是不能忍的。毕竟离开了补全和搜索引擎,我可是个什么都不会的废物啊。
但是内核模块显然不是 Cargo
项目,所以无法通过 Cargo.toml
来识别。不过 rust-analyzer
提供了其他方案,比如 rust-project.json
。可以透过这个 json 文件,声明项目的结构,来让 rust-analyzer
正常补全。
在内核目录中,可以通过下面这个命令来生成项目 json 文件。
make LLVM=1 rust-analyzer
然后,打开 samples/rust/rust_minimal.rs
,就可以了。
这里 coc-analyzer
已经开始工作了,不过会报错
Failed to spawn one or more proc-macro servers.
这个是因为 1.64
之前的宏展开不被 rust-analyzer
支持导致的。所以暂时只能用部分功能。等到 6.5 版本的内核发布后,应该可以支持全功能。
内核项目外
可是假如一个项目不在内核内部,那就无法使用 rust-analyzer
了。
在 rust_out_of_tree
中的 readme,包含了在外部项目中使用 rust-analyzer
的 patch
。这个是针对内核的 rust-project.json
生成脚本的补丁。可以在内核外部目录中也可以使用 rust_analyzer
。
mail list 地址:https://lore.kernel.org/rust-for-linux/20230121052507.885734-1-varmavinaym@gmail.com/
应用补丁
在 mail list 的 URL 加上 raw 后缀,可以获取到文本格式的 mail
cd ~/workspace/sources/linux-6.3.9
curl -sSL https://lore.kernel.org/rust-for-linux/20230121052507.885734-1-varmavinaym@gmail.com/raw -o patch-in-mail.txt
这里的 mail 格式是能直接用 patch
命令的。
假如是在 git 的目录下,可以用 git 来进行 patch
。
[vagrant@rocky9 linux-6.3.9]$ patch -b -p1 -i patch-in-mail.txt
patching file Makefile
Hunk #1 succeeded at 1856 (offset 25 lines).
Hunk #2 succeeded at 1916 (offset 33 lines).
Hunk #3 succeeded at 2054 (offset 36 lines).
patching file rust/Makefile
Hunk #1 succeeded at 360 with fuzz 2 (offset -29 lines).
patching file scripts/generate_rust_analyzer.py
Hunk #3 FAILED at 96.
Hunk #4 succeeded at 127 (offset 3 lines).
Hunk #5 succeeded at 136 (offset 3 lines).
1 out of 5 hunks FAILED -- saving rejects to file scripts/generate_rust_analyzer.py.rej
可以看到 generate_rust_analyzer.py
这个文件被 patch
失败了。
查看 scripts/generate_rust_analyzer.py.rej
文件
--- scripts/generate_rust_analyzer.py
+++ scripts/generate_rust_analyzer.py
@@ -96,25 +97,28 @@ def generate_crates(srctree, objtree, sysroot_src):
"exclude_dirs": [],
}
...
可以发现是 generate_crates
这个函数 patch 失败了,在 96 行附近。
来到这个代码部分,发现内核中的源码,和提供的 patch 是存在差异的。内核中的源码,多了一个 try...catch...的处理,导致了无法 patch。
移除相应的 try...catch...,再一次 patch 剩余的部分。
patch -p1 -i scripts/generate_rust_analyzer.py.rej
can't find file to patch at input line 3
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|--- scripts/generate_rust_analyzer.py
|+++ scripts/generate_rust_analyzer.py
--------------------------
File to patch: scripts/generate_rust_analyzer.py
最后的 File to patch 手动输入一下,猜是目录问题,懒得管了。至此,补丁完毕。
回到 rust_out_of_tree
项目目录,生成项目的 rust-project.json
cd ~/workspace/sources/rust-out-of-tree-module/
make -C ~/workspace/sources/linux-6.3.9/build/ LLVM=1 LLVM_IAS=1 M=$PWD rust-analyzer
这样子,打开编辑器,输入 kernel::
就有代码补全了。
初识 Rust-for-linux
rust 的内核代码都位于内核根目录下的 rust
文件夹中。
包含四个核心目录 alloc
,bindings
,kernel
以及 macro
。
其中 alloc 目录是 rust 的基础源码目录,提供 rust 最基本的功能。
bindings 目录是将 c 的内核 api 暴露给 rust
。其实我觉得这个是 kernel 与 rust 交互的核心内容。经过编译后,会在 bindings 目录下生成对应的 bindings_generated.rs
与 bindings_helpers_generated.rs
。这个工作应该是 bindgen 做的,具体怎么做的还没有来得及看,不过暂时并不想关心这部分内容。
kernel
目录,也就是核心目录。内容很少,现在能做的可能只是 printk
。一眼望过去是如此。
macros
目录,定义了一些宏,一共就三个宏
Hello world
到此,可以新建一个 hello world 了。
mkdir hello_world && cd hello_world && touch hello_world.rs
Kbuild
obj-m =: hello_world.o
rust-project.json
这个使用如下命令自动生成,这一步必须要在 *.rs
文件创建后执行,否则不会被关联。每次新的 *.rs
文件被创建,可能都需要重新执行。
make -C ~/workspace/sources/linux-6.3.9/build/ KDIR=~/workspace/sources/linux-6.3.9/build/ LLVM=1 LLVM_IAS=1 M=$PWD rust-analyzer
hello_world.rs
//! Rust hello world module
use kernel::str::CString;
use kernel::{prelude::*, fmt};
/// `name`可以理解成模块名称,但是也是一个变量,要符合变量名的基本规则,不能包含
/// 空格之类的。方便的话直接写type的下划线形式就好了。
/// `type` 就是这个模块的类型,与下面的struct相对应。
/// `description`: 随便写就是描述
/// `license`: 开源协议,这里要填写与`GPL2`相兼容的协议,否则会告警。(but who cares?)
module! {
name: "rs_hello_world",
type: RsHelloWorld,
description: "Hello wolrd for rust",
license: "GPL",
}
/// 与module中的type相对应
struct RsHelloWorld {
user_data: CString
}
impl kernel::Module for RsHelloWorld {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust Hello Wolrd (init)\n");
let user_data = CString::try_from_fmt(fmt!("test"))?;
Ok(RsHelloWorld { user_data })
}
}
impl Drop for RsHelloWorld {
fn drop(&mut self) {
match self.user_data.to_str() {
Ok(user_data) => pr_warn!("User data is {}\n", user_data),
_ => pr_crit!("Error on get user data")
}
pr_info!("Rust Hello Wolrd (exit)\n");
}
}
加载内核模块
~ # modprobe hello_world
hello_world: loading out-of-tree module taints kernel.
rs_hello_world: Rust Hello Wolrd (init)
~ # rmmod hello_world
rs_hello_world: User data is test
rs_hello_world: Rust Hello Wolrd (exit)
主要实现 kernel::Module
这个 trait。然后实现 Drop
的 trait。相对应的是内核模块加载与卸载。
CString 类型
在 Rust for Linux
中没有 String 方法,但是有对应的 CString
方法。也还行把。就是字写的有点长。
这里可以调用 to_str()方法,讲其转为 &str
。来让内核打印。这里的 to_str()
方法,其实是在 CStr 中实现的。
这里的核心是 Cstring 的 deref
使用了 transmute
,转成了 CStr
,所以看上去像实现了继承。
写了一个简单的 demo——Rust 伪继承
核心代码如下
impl Deref for B {
type Target = A;
fn deref(&self) -> &Self::Target {
unsafe { core::mem::transmute(&self.0) }
}
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于