从零开始的 rust 内核开发(三)

本贴最后更新于 366 天前,其中的信息可能已经时移俗易

#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-analyzerpatch。这个是针对内核的 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,bindingskernel 以及 macro

  其中 alloc 目录是 rust 的基础源码目录,提供 rust 最基本的功能。

  bindings 目录是将 c 的内核 api 暴露给 rust。其实我觉得这个是 kernel 与 rust 交互的核心内容。经过编译后,会在 bindings 目录下生成对应的 bindings_generated.rsbindings_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) }
    }
}

  ‍

  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    58 引用 • 22 回帖 • 3 关注
  • 内核
    10 引用 • 14 回帖
  • kernel
    6 引用 • 2 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...