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的core
和 alloc
library,因此需要rust的源码
kernel编码使用的
core
和alloc
同rust自带的不是同一套代码,kernel编码使用的core
和alloc
源码就在 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的选择界面
我们选择新的kernel启动
进入系统查看,当前已经在使用新的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相关内容去掉
代码里面的