likes
comments
collection
share

使用rust编写一个linux module: Hello World

作者站长头像
站长
· 阅读数 14

1.背景介绍

Linux Kernel 6.1中已经支持Rust编程,成为除C以外的另一个新增语言

Linux Kernel v6.1 change 描述: Initial_support_for_the_Rust_programming_language

但是当前的支持有限: Next steps for Rust in the kernel Torvalds said that he would like to see a minimal merge just to get the infrastructure into the kernel and allow developers to start playing with it. It should build, but shouldn't do much of anything beyond the "hello, world" stage

所以当前只能支持写"Hello Word"程度的简单代码,但是这不妨碍我们一试。 下面我们看看如何用Rust编写一个简单的in-tree kernel module,并加载,查看"Hello world"能够被成功打印。

2 编译Linux Kernel

参考: www.kernel.org/doc/html/v6…

在编写我们的第一个Rust Kernel Module之前,我们先看如何编译一个新的kernel,并使用新的Kernel启动我们的虚拟机。

推荐使用一个Linux的虚拟机进行Kernel的开发,防止系统无法启动,影响当前的主力电脑,这里我们使用VirtualBox加载了Ubuntu22作为开发环境

!!!注意!!!: VirtualBox 要给虚拟机分配足够多的CPU,防止编译过慢,我当前是给Ubuntu Guest分配了8个CPU,10G内存(我的CPU为 AMD3700

2.1 下载Linux Kernel代码

我们使用清华大学的镜像来下载Linux Kernel Git仓库, 目标文件夹命名为kernel

参考: mirrors.tuna.tsinghua.edu.cn/help/linux.…

git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux.git kernel

Cloning into 'kernel'...
remote: Enumerating objects: 9606279, done.
remote: Total 9606279 (delta 0), reused 0 (delta 0), pack-reused 9606279
Receiving objects: 100% (9606279/9606279), 1.93 GiB | 42.08 MiB/s, done.
Resolving deltas: 100% (8182229/8182229), done.
Updating files: 100% (81099/81099), done.

当前最新的发布Linux Kernel是v6.4, 既然我们已经闯入了Rust Kernel的全新领域,就让我们使用最新的Kernel版本来做开发

当前Kernel的最新版本:www.kernel.org/, 在写这篇文章的时候stable是 v6.4.11

// Checkout v6.4 kernel 版本代码
danny@kernel:~$ cd kernel/

danny@kernel:~/kernel$ git checkout v6.4
Updating files: 100% (17558/17558), done.
Note: switching to 'v6.4'.

//创建一个新的git分支
danny@kernel:~/kernel$ git checkout -b rust_module
Switched to a new branch 'rust_module'

danny@kernel:~/kernel$ git branch
  master
* rust_module

2.2 安装工具

2.2.1 安装基本工具

danny@ubuntu:~/kernel$ sudo apt install build-essential flex bison libelf-dev libssl-dev

2.2.2 安装rust编译链

因为我们要编译rust代码,我们需要安装rust工具,kernel源码里面有个make target检查当前系统的rust满足度

danny@kernel:~$ cd kernel

danny@kernel:~/kernel$ make LLVM=1 rustavailable
***
*** Rust compiler 'rustc' could not be found.
***
make: *** [Makefile:1826: rustavailable] Error 1

可以看到我们当前不满足,让我们安装rust工具链

//设置镜像
danny@kernel:~/kernel$ export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static

//安装rustup rust cargo等
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
info: downloading installer

Welcome to Rust!
...
Current installation options:


   default host triple: x86_64-unknown-linux-gnu
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation

上面我们选择1,安装rust stable, 然后运行source "$HOME/.cargo/env"更新PATH

danny@kernel:~/kernel$ source "$HOME/.cargo/env"

//默认安装的版本
danny@kernel:~/kernel$ rustc --version
rustc 1.71.1 (eb26296b5 2023-08-03)

Kernel的编译对rustc的版本有特定的要求,我们使用rustup重新安装指定版本

danny@kernel:~/kernel$ rustup override set $(scripts/min-tool-version.sh rustc)
info: syncing channel updates for '1.62.0-x86_64-unknown-linux-gnu'
info: latest update on 2022-06-30, rust version 1.62.0 (a8314ef7d 2022-06-27)
info: downloading component 'cargo'

//rustc已经覆盖为指定的版本
danny@kernel:~/kernel$ rustc --version
rustc 1.62.0 (a8314ef7d 2022-06-27)

2.2.3 安装rust library

由于我们需要编译rust的corealloc library,因此需要rust的源码

kernel编码使用的corealloc同rust自带的不是同一套代码,kernel编码使用的corealloc源码就在 kernel的git仓库里面,是一个缩减版本,很多功能没有,例如没有rust里面的String

rustup component add rust-src

2.2.4 安装LLVM

一般kernel的编译使用GCC, 但是当前rust只支持使用clang编译,因此我们使用LLVM编译linux kernel

danny@kernel:~/kernel$ sudo apt install llvm lld clang

2.2.5 安装bindgen

由于rust需要调用kernel里面其他的C代码,因此需要bindgen来生成C的binding

此处是我们第一次使用cargo命令,建议配置cargo镜像: 在 ~/.cargo/文件夹中创建config文件,文件内容为

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
danny@ubuntu:~/kernel$ cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen

首次使用镜像的时候,会更新镜像的index(卡在Updating 'ustc' index不动),建议耐心等待,时间为一分钟左右

2.3 Kernel编译配置项

Kernel作为一个通用的软件,为了适配不同的场景、平台、架构,做了非常多的配置项(特性开关)。 每个linux发行版本都有不同的选项,为了节省编译时间,我们通过以下方法,创建一个.config文件,这个文件只包含了我们当前系统加载的kernel module,而不是Ubuntu的全量kernel module

//查看当前记载的module
danny@kernel:~/kernel$ lsmod                                                                                         Module                  Size  Used by                                                                                binfmt_misc            24576  1                                                                                      intel_rapl_msr         20480  0                                                                                      intel_rapl_common      40960  1 intel_rapl_msr                                                                       snd_intel8x0           45056  0                                                                                      snd_ac97_codec        180224  1 snd_intel8x0
joydev                 32768  0
ac97_bus               16384  1 snd_ac97_codec
snd_pcm               143360  2 snd_intel8x0,snd_ac97_codec
snd_timer              40960  1 snd_pcm
input_leds             16384  0
snd                   106496  4 snd_intel8x0,snd_timer,snd_ac97_codec,snd_pcm
vboxguest              45056  0
serio_raw              20480  0
......

//将上面的输出保存到文件里面
danny@ubuntu:~/kernel$ lsmod > /tmp/lsmod.now

//使用上面的文件创建 .config 文件
danny@kernel: cd ~/kernel
danny@kernel:~/kernel$ make LSMOD=/tmp/lsmod.now localmodconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/confdata.o
  HOSTCC  scripts/kconfig/expr.o
  LEX     scripts/kconfig/lexer.lex.c
  YACC    scripts/kconfig/parser.tab.[ch]
  HOSTCC  scripts/kconfig/lexer.lex.o
  HOSTCC  scripts/kconfig/menu.o
  HOSTCC  scripts/kconfig/parser.tab.o
  HOSTCC  scripts/kconfig/preprocess.o
  HOSTCC  scripts/kconfig/symbol.o
  HOSTCC  scripts/kconfig/util.o
  HOSTLD  scripts/kconfig/conf
using config: '.config'
*
* Restart config...
*
*
* PCI GPIO expanders
*
AMD 8111 GPIO driver (GPIO_AMD8111) [N/m/y/?] n
BT8XX GPIO abuser (GPIO_BT8XX) [N/m/y/?] (NEW)
......
#
# configuration written to .config
#

由于我们用的Kernel代码是最新的,里面可能新增了配置项,现有config文件里面没有,kernel编译工具会提示我们选择(例如: AMD 8111 GPIO driver (GPIO_AMD8111) [N/m/y/?]),我们都选默认(一直按住Enter,直到没有选择提示)

上面可以看到我们的.config文件创建成功了(configuration written to .config), 我们还需要修改一下,经各种key相关的配置清空:

修改前修改后
CONFIG_SYSTEM_REVOCATION_KEYS="debian/canonical-revoked-certs.pem"CONFIG_SYSTEM_REVOCATION_KEYS=""

2.3 编译kernel

2.3.1 我们现在可以开始编译了

//我们给虚拟机分配了8个CPU,所以这里我们选择8
time make LLVM=1 -j8
.....
Kernel: arch/x86/boot/bzImage is ready  (#2)

real    7m46.982s
user    54m8.980s
sys     4m40.624s

命令行里面的time是为了记录编译时间, 可以看到我的电脑配置上面用时为7分钟

通过命令行,我们可以看到我们编译了哪些Module

danny@kernel:~/kernel$ find . -name "*.ko"                                                                           ./fs/autofs/autofs4.ko                                                                                               ./fs/pstore/pstore_zone.ko                                                                                           ./fs/pstore/ramoops.ko                                                                                               ./fs/pstore/pstore_blk.ko                                                                                            ./fs/btrfs/btrfs.ko                     
...... 

//一共编译了78个module
danny@kernel:~/kernel$ find . -name "*.ko" | wc
     78      78    2335

2.3.2 下一步就是安装Module,kernel默认的安装路径是/lib/modules

danny@kernel:~/kernel$ sudo make modules_install
  INSTALL /lib/modules/6.4.0/kernel/arch/x86/crypto/aesni-intel.ko                                                     SIGN    /lib/modules/6.4.0/kernel/arch/x86/crypto/aesni-intel.ko 
  .....

//查看安装路径:5.15.0-67-generic是Ubuntu自带的kernel module路径,6.4.0就是我们的Kernel module路径
danny@kernel:~/kernel$ ls /lib/modules/
5.15.0-67-generic  6.4.0

2.3.3 再安装kernel

danny@kernel:~/kernel$ sudo make install
  INSTALL /boot
  ........

2.4 配置GRUB

此时如果我们重启,系统会默认使用最新的kernel。 但是由于我们要进行kernel开发,有可能导致系统无法启动,建议采用GRUB来使能启kernel选择:默认使用Ubuntu系统自带的Kernel,可以手工选择我们的新Kernel。 这样当我们的kernel编译有问题的时候,还可以启动系统。

2.4.1 使能GRUB开机自动显示

//备份系统自带GRUB
danny@kernel:~/kernel$ sudo cp /etc/default/grub /etc/default/grub.orig

//修改GRUB
danny@kernel:~/kernel$ sudo vim /etc/default/grub

修改前

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX=""

修改后

GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 5.15.0-79-generic"
GRUB_TIMEOUT_STYLE=menu
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX=""

新增的配置项GRUB_DEFAULT要和当前的系统保持一致,这个是默认的启动 Kernel,当GRUB界面出现,5秒之内我们没有做任何选择的时候,默认使用这个 查看当前系统版本信息

danny@kernel:~$ uname -r
5.15.0-79-generic

运行命令使上面的配置生效sudo update-grub

2.5 重启

sudo reboot

!!!注意!!! 强制关机上面的GRUB配置不生效,必须运行reboot命令

重启后,可以看到GRUB的选择界面

使用rust编写一个linux module: Hello World

我们选择新的kernel启动

使用rust编写一个linux module: Hello World

进入系统查看,当前已经在使用新的Kernel

danny@kernel:~$ uname -r
6.4.0

3 编写Rust Hello Module

3.1 编写代码

当前linux kernel已经自带了一些Rust的实例,路径为 $KERNEL_HOME/samples/rust

danny@kernel:~/kernel$ ls samples/rust/
Kconfig  Makefile  hostprogs  rust_minimal.rs  rust_print.rs

按照惯例,我们也在下面新建一个文件

danny@kernel:~/kernel$ vim samples/rust/rust_hello.rs

并将下面的代码拷贝进去:

use kernel::prelude::*;

module! {
    type: RustHello,
    name: "rust_hello",
    author: "Rust for Linux Contributors",
    description: "Rust hello sample",
    license: "GPL v2",
}

struct RustHello<'a> {
    message: &'a str,
}

impl<'a> kernel::Module for RustHello<'a> {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        pr_info!("Rust hello sample (init)\n");
        pr_info!("Am I built-in? {}\n", !cfg!(MODULE));

        Ok(RustHello {
            message: "on the heap!",
        })
    }
}

impl<'a> Drop for RustHello<'a> {
    fn drop(&mut self) {
        pr_info!("My message is {:?}\n", self.message);
        pr_info!("Rust hello sample (exit)\n");
    }
}

可以看到在struct RustHello我们没有使用String类型,原因是因为当前的rust linux Kernel开发,依赖的是这个三个基本Libary:

注意区别上面的三个crate和rust中的三个标准crate同名,但是他们是rust标准crate的不完全体,代码就在linux git仓库的rust目录下面

danny@kernel:~/kernel$ tree rust/ -L 1
rust/
...
├── alloc
....
├── kernel
...

而标准alloc crate里面的String没有被迁移过来, 虽然Documentation迁移过来的时候没有把String相关内容去掉

代码里面的<'a>是表示变量的生命周期,这里的意思是说,message指针对应的内存,生命周期和struct一样:struct被释放是,指针的内存也要同时释放,相关的解释可以参考:Crust of Rust

3.2 增加Kernel的Module选项

我们如何告诉Kernel编译的时候,需要把我们新的moduel编译出来呢,通过修改Kbuild的配置文件

此处我们不在展开介绍Kbuild,有兴趣的可以参考Linux Kernel Programming: A practical guide to kernel internals, writing kernel modules, and synchronization, 2nd Editio的Chapter 2和 Chapter 3相关介绍

我们首先需要使能rust支持: 0. make menuconfig 弹出配置界面

  1. 关掉MODEVERSIONS: Enable Loadble module support -> Module versioning support
  2. 使能rust support : General setup --> Rust support
  3. 使能rust sample code: kernel hacking --> Sample Kernel code --> rust Samples

此处一定要按空格键,使能后进入,才能显示rust相关实例代码

使用rust编写一个linux module: Hello World

  1. Enter进入查看可以编译那些rust代码

使能/去使能使用空格键:

  • _ 表示没有使能
  • * 表示使能

编译为Module,按M键(也可以按空格键循环):

  • _ 表示不编译
  • M表示编译为Module
  • *表示直接编译到Kernel

进入菜单使用Enter, 退出菜单使用Esc,一路Esc回到主界面,再次按Ecs会弹出保存对话框

当使能rust support后,我们看到rust sample code里面只有rust_minimalrust_print,没有我们的新创建的rust_hello

danny@kernel:~/kernel$ ls -l samples/rust/*.rs
-rw-rw-r-- 1 danny danny  695 Aug 19 00:54 samples/rust/rust_hello.rs
-rw-rw-r-- 1 danny danny  852 Aug 18 22:47 samples/rust/rust_minimal.rs
-rw-rw-r-- 1 danny danny 2311 Aug 18 22:47 samples/rust/rust_print.rs

这是由于我们需要添加Kbuild的配置

//修改sample code配置文件
danny@kernel:~/kernel$ vim samples/rust/Kconfig

//增加如下配置
config SAMPLE_RUST_HELLO
        tristate "Rust Hello"
        help
          This option builds the Rust hello sample.

          To compile this as a module, choose M here:
          the module will be called rust_print.

          If unsure, say N.

再次启动Kbuild, 可以看到rust hello,选择为M模式

使用rust编写一个linux module: Hello World

上面只是告诉Kbuild我们要编译rust,但是没有告诉Kbuild我们的rust hello的源代码文件在哪,此时需要修改Make文件

//修改Make文件,添加文件位置
danny@kernel:~/kernel$ vim samples/rust/Makefile

//添加下面一行
obj-$(CONFIG_SAMPLE_RUST_HELLO)                 += rust_hello.o

3.3 编译rust hello module

重新编译Kernel

因为我们修改了.config文件,可能会弹出新的配置选项,直接Etner一路到底

danny@kernel:~/kernel$ time make LLVM=1 -j8
.....
Kernel: arch/x86/boot/bzImage is ready  (#3)

real    7m8.451s
user    47m27.950s
sys     3m29.314s

此时我们如果查看 samples/rust/下面, 会看到多了rust的module ko文件,有我们选择的rust_minimalrust_hello

danny@kernel:~/kernel$ ls samples/rust/*.ko
samples/rust/rust_hello.ko  samples/rust/rust_minimal.ko

安装module到/lib/modules/6.4.0-dirty/kernel/kernel/

danny@kernel:~/kernel$ sudo make modules_install

danny@kernel:~/kernel$ ls /lib/modules/6.4.0-dirty/kernel/samples/rust/
rust_hello.ko  rust_minimal.ko

新的安装位置变成了 6.4.0-dirty(如果你没有commit) 或者 6.4.0+(如果你commit了)

3.4 安装新的kernel

danny@kernel:~/kernel$ sudo make install

!!!注意!!!: 虽然我们没有修改kernel的C代码,但是要重新安装

3.5 设置Module自动加载

当前我们的module在启动的时候不会自动加载,让我们修改/etc/modules-load.d/modules.conf文件,来自动加载新的module

danny@kernel:~/kernel$ sudo vim /etc/modules-load.d/modules.conf

修改后的文件:

danny@kernel:~/kernel$ cat /etc/modules-load.d/modules.conf
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
rust_hello

3.5 重启,并选择新的kernel

使用rust编写一个linux module: Hello World

!!!注意!!! 选择新的6.4.0-dirty 而不是6.4.0

danny@ubuntu:~$ uname -r
6.4.0-dirty


//查看module已经加载
danny@kernel:~$ lsmod | grep hello
rust_hello             12288  0
//查看module输出到kernel memory ring buffer
danny@kernel:~$ sudo dmesg | grep  hello
[sudo] password for danny:
[    4.380255] rust_hello: Rust hello sample (init)
[    4.380650] rust_hello: Am I built-in? false

kernel memory ring buffer 是module默认的stdout,我们可以使用dmesg命令来查看输出的结果,你可以尝试修改rust_hello.rs打印不同的内容,重新编译启动

完结

好了,上面我们就完成了rust kernel module hello world的编写,有任何问题请在评论区留言,对其中的错误会及时更新