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

2023年 8月 21日 60.6k 0

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的选择界面

grub_menu.png

我们选择新的kernel启动

select_new_kernel.png

进入系统查看,当前已经在使用新的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 {
    fn init(_module: &'static ThisModule) -> Result {
        pr_info!("Rust hello sample (init)n");
        pr_info!("Am I built-in? {}n", !cfg!(MODULE));

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

impl {
    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:

  • kernal crate
  • core crate 注意URL,区别于rust标准的crate rust core, 里面写的特性,不一定都支持
  • alloc crate 注意URL,区别于rust标准的crate rust alloc,里面写的特性,不一定都支持

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

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

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

代码里面的

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论