Rust for Linux 学习笔记系列 - 第一章 开发环境搭建

首先声明一下,本系列笔记学习是记录学习过程中的内容,是从互联网收集到的各种资料进行学习整理的笔记,所以我会把学习资源列到最后。当然肯定会有一些错误,如果有错误的地方欢迎指正,请 TG 联系 genedna

Rust for Linux [1] 的环境搭建,是我看完 Linux Foundation 的教学视频后跟着一步步做后总结整理的,大家有时间的话推荐还是看一下 原视频 [2],这样会更加清晰。视频中 Wedson Almeida Filho 是使用了一个 Ubuntu 21.04 的虚拟机来进行演示,我在本地测试的环境是安装在小米笔记本上的 Ubuntu 22.10 环境。虽然两个系统环境有点细节上的差异,但是我参照执行后并没有任何问题。

1. 安装依赖的库和软件

sudo apt install git flex bison clang llvm lld libelf-dev qemu-kvm

2. 安装 Rust 环境

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

当前我安装的是 Rust1.65.0 版本,可以通过 rustc --version 查看。根据 Wedson 在视频里面的解释,目前 Rust for Linux 使用的最小版本是 1.62.0,但是目前没有测试更高版本是否有问题. 所以我这里安装的版本是比较新的, 所以需要安装一个较低的版本。

在安装较低版本前,先 Clone Rust for Linux 的代码到本地,为此我创建了一个 GitHub 的目录。

$ mkdir GitHub
$ cd GitHub
$ git clone --depth=1 https://github.com/Rust-for-Linux/linux.git
正克隆到 'linux'...
remote: Enumerating objects: 83487, done.
remote: Counting objects: 100% (83487/83487), done.
remote: Compressing objects: 100% (75322/75322), done.
remote: Total 83487 (delta 7737), reused 72487 (delta 7313), pack-reused 0
接收对象中: 100% (83487/83487), 231.81 MiB | 6.68 MiB/s, 完成.
处理 delta 中: 100% (7737/7737), 完成.
正在更新文件: 100% (78804/78804), 完成.

这时候我们可以看到当前分之是 rust 分支,这个分支是 Rust for Linux 的主分支,我们可以通过 git branch 查看当前分支,注意不要切换到其它分之。

然后进入到 linux 目录下,安装最小版本的 Rust 编译环境。

$ 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'
  6.6 MiB /   6.6 MiB (100 %)   4.6 MiB/s in  1s ETA:  0s
info: downloading component 'clippy'
info: downloading component 'rust-docs'
 18.3 MiB /  18.3 MiB (100 %)   8.6 MiB/s in  2s ETA:  0s
info: downloading component 'rust-std'
 26.0 MiB /  26.0 MiB (100 %)   6.2 MiB/s in  4s ETA:  0s
info: downloading component 'rustc'
 54.1 MiB /  54.1 MiB (100 %)   8.2 MiB/s in  7s ETA:  0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 18.3 MiB /  18.3 MiB (100 %)   9.5 MiB/s in  1s ETA:  0s
info: installing component 'rust-std'
 26.0 MiB /  26.0 MiB (100 %)  12.7 MiB/s in  1s ETA:  0s
info: installing component 'rustc'
 54.1 MiB /  54.1 MiB (100 %)  15.4 MiB/s in  3s ETA:  0s
info: installing component 'rustfmt'
info: override toolchain for '/home/eli/GitHub/linux' set to '1.62.0-x86_64-unknown-linux-gnu'

  1.62.0-x86_64-unknown-linux-gnu installed - rustc 1.62.0 (a8314ef7d 2022-06-27)
$ rustc --version
rustc 1.62.0 (a8314ef7d 2022-06-27)

使用 make LLVM=1 rustavailable 检查 Rust 环境的其它组件是否完整。

$ make LLVM=1 rustavailable
***
*** Rust bindings generator 'bindgen' could not be found.
***
make[1]: *** [rustavailable] Error 1
make: *** [__sub-make] Error 2

bindgen 是自动生成 Rust FFI 与 C/C++ 库绑定的工具,内核 C 的代码绑定是使用它生成的,所以需要安装指定版本的 bindgen。 在之前安装系统环境的时候我们就安装了 LLVMclang,但是所以这里不再提示相关的错误。

$ cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen
  Downloaded bindgen v0.56.0
  Downloaded 1 crate (198.3 KB) in 3.85s
    Updating crates.io index
  Installing bindgen v0.56.0
  Downloaded memchr v2.3.4
  Downloaded proc-macro2 v1.0.24
  Downloaded clap v2.33.3
  Downloaded glob v0.3.0
  Downloaded regex v1.4.2
  Downloaded rustc-hash v1.1.0
  Downloaded thread_local v1.0.1
  Downloaded which v3.1.1
  Downloaded version_check v0.9.2
  Downloaded termcolor v1.1.0
  Downloaded libc v0.2.80
  Downloaded aho-corasick v0.7.15
  Downloaded regex-syntax v0.6.21
  Downloaded shlex v0.1.1
  Downloaded peeking_take_while v0.1.2
  Downloaded log v0.4.11
  Downloaded bitflags v1.2.1
  Downloaded humantime v2.0.1
  Downloaded lazycell v1.3.0
  Downloaded cexpr v0.4.0
  Downloaded ansi_term v0.11.0
  Downloaded nom v5.1.2
  Downloaded libloading v0.6.5
  Downloaded env_logger v0.8.1
  Downloaded clang-sys v1.0.3
  Downloaded 25 crates (1.9 MB) in 3.65s
   Compiling memchr v2.3.4
   Compiling libc v0.2.80
   Compiling glob v0.3.0
   Compiling version_check v0.9.2
   Compiling lazy_static v1.4.0
   Compiling log v0.4.11
   Compiling bitflags v1.2.1
   Compiling proc-macro2 v1.0.24
   Compiling cfg-if v1.0.0
   Compiling cfg-if v0.1.10
   Compiling unicode-xid v0.2.1
   Compiling unicode-width v0.1.8
   Compiling regex-syntax v0.6.21
   Compiling humantime v2.0.1
   Compiling ansi_term v0.11.0
   Compiling strsim v0.8.0
   Compiling bindgen v0.56.0
   Compiling vec_map v0.8.2
   Compiling termcolor v1.1.0
   Compiling shlex v0.1.1
   Compiling rustc-hash v1.1.0
   Compiling lazycell v1.3.0
   Compiling peeking_take_while v0.1.2
   Compiling thread_local v1.0.1
   Compiling libloading v0.6.5
   Compiling textwrap v0.11.0
   Compiling nom v5.1.2
   Compiling clang-sys v1.0.3
   Compiling aho-corasick v0.7.15
   Compiling quote v1.0.7
   Compiling atty v0.2.14
   Compiling which v3.1.1
   Compiling clap v2.33.3
   Compiling regex v1.4.2
   Compiling env_logger v0.8.1
   Compiling cexpr v0.4.0
    Finished release [optimized] target(s) in 3m 30s
  Installing /home/eli/.cargo/bin/bindgen
   Installed package `bindgen v0.56.0` (executable `bindgen`)

再次使用 make LLVM=1 rustavailable 检查 Rust 环境的其它组件是否完整,会发现需要安装 Rust 的源代码来交叉编译 corealloc 库。

$ make LLVM=1 rustavailable
***
*** Source code for the 'core' standard library could not be found
*** at '/Users/eli/.rustup/toolchains/1.62.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/lib.rs'.
***
make[1]: *** [rustavailable] Error 1
make: *** [__sub-make] Error 2
$ rustup component add rust-src
info: downloading component 'rust-src'
info: installing component 'rust-src'

最后检查 Rust 环境已经准备好了。

$ make LLVM=1 rustavailable
Rust is available!

如果提交 PRRust for Linux ,需要对代码进行格式化和 Clippy 检查,所以需要安装 rustfmtclippy 组件。

$ rustup component add rustfmt
$ rustup component add clippy

3. 安装 Busybox 环境

光有 Rust 环境还不够,还需要安装 Busybox 环境。 Busybox 是一个非常小的 Linux 发行版,它包含了 Linux 系统中的大部分命令,比如 lscatgrep 等等。 调试环境的主要是利用 QEMURust for Linux 编译出的内核,加上 Busybox 的文件系统运行在虚拟机中,这样就可以在虚拟机中进行调试了。

首先是下载 Busybox 的源代码。

$ git clone --depth=1 https://github.com/mirror/busybox.git
正克隆到 'busybox'...
remote: Enumerating objects: 2301, done.
remote: Counting objects: 100% (2301/2301), done.
remote: Compressing objects: 100% (1904/1904), done.
remote: Total 2301 (delta 119), reused 1443 (delta 60), pack-reused 0
接收对象中: 100% (2301/2301), 3.33 MiB | 1.44 MiB/s, 完成.
处理 delta 中: 100% (119/119), 完成.

进入到 Busybox 的源代码目录,使用默认的配置初始化 .config文件。

$ cd busybox
$ make defconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/basic/split-include
  HOSTCC  scripts/basic/docproc
  GEN     include/applets.h
  GEN     include/usage.h
  GEN     modutils/Kbuild
  GEN     modutils/Config.in
  GEN     e2fsprogs/Kbuild
  GEN     e2fsprogs/Config.in
  GEN     shell/Kbuild
  GEN     shell/Config.in
  GEN     console-tools/Kbuild
  GEN     console-tools/Config.in
  GEN     editors/Kbuild
  GEN     editors/Config.in
  GEN     runit/Kbuild
  GEN     runit/Config.in
  GEN     archival/Kbuild
  GEN     archival/Config.in
  GEN     archival/libarchive/Kbuild
  GEN     scripts/Kbuild
  GEN     klibc-utils/Kbuild
  GEN     klibc-utils/Config.in
  GEN     debianutils/Kbuild
  GEN     debianutils/Config.in
  GEN     libbb/Kbuild
  GEN     libbb/Config.in
  GEN     findutils/Kbuild
  GEN     findutils/Config.in
  GEN     coreutils/Kbuild
  GEN     coreutils/Config.in
  GEN     coreutils/libcoreutils/Kbuild
  GEN     miscutils/Kbuild
  GEN     miscutils/Config.in
  GEN     init/Kbuild
  GEN     init/Config.in
  GEN     procps/Kbuild
  GEN     procps/Config.in
  GEN     mailutils/Kbuild
  GEN     mailutils/Config.in
  GEN     sysklogd/Kbuild
  GEN     sysklogd/Config.in
  GEN     networking/Kbuild
  GEN     networking/Config.in
  GEN     networking/libiproute/Kbuild
  GEN     networking/udhcp/Kbuild
  GEN     networking/udhcp/Config.in
  GEN     libpwdgrp/Kbuild
  GEN     loginutils/Kbuild
  GEN     loginutils/Config.in
  GEN     selinux/Kbuild
  GEN     selinux/Config.in
  GEN     util-linux/Kbuild
  GEN     util-linux/Config.in
  GEN     util-linux/volume_id/Kbuild
  GEN     util-linux/volume_id/Config.in
  GEN     applets/Kbuild
  GEN     printutils/Kbuild
  GEN     printutils/Config.in
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/kxgettext.o
  HOSTCC  scripts/kconfig/mconf.o
  SHIPPED scripts/kconfig/zconf.tab.c
  SHIPPED scripts/kconfig/lex.zconf.c
  SHIPPED scripts/kconfig/zconf.hash.c
  HOSTCC  scripts/kconfig/zconf.tab.o
  HOSTLD  scripts/kconfig/conf

# 此处略去 1000 行

scripts/kconfig/conf -d Config.in

对于太多的输出,笔记中做了适当的截取,会保留关键的输出。

通过 menuconfig 命令调整参数实现编译静态的二进制文件。

$ make menuconfig

busy-box-menuconfig-1

busy-box-menuconfig-2

执行 make install 命令后会整个发行版会安装在 _install 目录中。

$ make install
  ./_install//bin/arch -> busybox
  ./_install//bin/ash -> busybox
  ./_install//bin/base32 -> busybox
  ./_install//bin/base64 -> busybox
  ./_install//bin/cat -> busybox
  ./_install//bin/chattr -> busybox
  ./_install//bin/chgrp -> busybox

# 此处略去 1000 行

--------------------------------------------------
You will probably need to make your busybox binary
setuid root to ensure all configured applets will
work properly.
--------------------------------------------------

$ ls _install/
bin  linuxrc  sbin  usr

这样获得了一个基本可用的发行版文件系统。

4. 编译内核,加入 Rust 支持

因为要引导 Busybox 的文件系统,所以先使用 qemu-busybox-min.config 设置生成基本的内核配置文件。

$ cd linux
$ make LLVM=1 allnoconfig qemu-busybox-min.config
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/confdata.o
  HOSTCC  scripts/kconfig/expr.o
  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
#
# configuration written to .config
#
Using .config as base
Merging ./kernel/configs/qemu-busybox-min.config
Value of CONFIG_SMP is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_SMP is not set
New value: CONFIG_SMP=y

Value of CONFIG_PRINTK_TIME is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_PRINTK_TIME is not set
New value: CONFIG_PRINTK_TIME=y

Value of CONFIG_PCI is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_PCI is not set
New value: CONFIG_PCI=y

Value of CONFIG_BLK_DEV_INITRD is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_BLK_DEV_INITRD is not set
New value: CONFIG_BLK_DEV_INITRD=y

Value of CONFIG_BINFMT_ELF is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_BINFMT_ELF is not set
New value: CONFIG_BINFMT_ELF=y

Value of CONFIG_DEVTMPFS is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_DEVTMPFS is not set
New value: CONFIG_DEVTMPFS=y

Value of CONFIG_NET is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_NET is not set
New value: CONFIG_NET=y

Value of CONFIG_DEBUG_KERNEL is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_DEBUG_KERNEL is not set
New value: CONFIG_DEBUG_KERNEL=y

Value of CONFIG_INPUT_EVDEV is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_INPUT_EVDEV is not set
New value: CONFIG_INPUT_EVDEV=y

Value of CONFIG_INPUT_KEYBOARD is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_INPUT_KEYBOARD is not set
New value: CONFIG_INPUT_KEYBOARD=y

Merging ./arch/x86/configs/qemu-busybox-min.config
Value of CONFIG_64BIT is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_64BIT is not set
New value: CONFIG_64BIT=y

Value of CONFIG_ACPI is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_ACPI is not set
New value: CONFIG_ACPI=y

Value of CONFIG_SERIAL_8250 is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_SERIAL_8250 is not set
New value: CONFIG_SERIAL_8250=y

Value of CONFIG_HYPERVISOR_GUEST is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_HYPERVISOR_GUEST is not set
New value: CONFIG_HYPERVISOR_GUEST=y

Value of CONFIG_CMDLINE_BOOL is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_CMDLINE_BOOL is not set
New value: CONFIG_CMDLINE_BOOL=y

#
# merged configuration written to .config (needs make)
#
#
# configuration written to .config
#

然后加入 Rust 的配置。

$ make LLVM=1 allnoconfig qemu-busybox-min.config rust.config
#
# configuration written to .config
#
Using .config as base
Merging ./kernel/configs/qemu-busybox-min.config
Value of CONFIG_SMP is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_SMP is not set
New value: CONFIG_SMP=y

Value of CONFIG_PRINTK_TIME is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_PRINTK_TIME is not set
New value: CONFIG_PRINTK_TIME=y

Value of CONFIG_PCI is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_PCI is not set
New value: CONFIG_PCI=y

Value of CONFIG_BLK_DEV_INITRD is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_BLK_DEV_INITRD is not set
New value: CONFIG_BLK_DEV_INITRD=y

Value of CONFIG_BINFMT_ELF is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_BINFMT_ELF is not set
New value: CONFIG_BINFMT_ELF=y

Value of CONFIG_DEVTMPFS is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_DEVTMPFS is not set
New value: CONFIG_DEVTMPFS=y

Value of CONFIG_NET is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_NET is not set
New value: CONFIG_NET=y

Value of CONFIG_DEBUG_KERNEL is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_DEBUG_KERNEL is not set
New value: CONFIG_DEBUG_KERNEL=y

Value of CONFIG_INPUT_EVDEV is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_INPUT_EVDEV is not set
New value: CONFIG_INPUT_EVDEV=y

Value of CONFIG_INPUT_KEYBOARD is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_INPUT_KEYBOARD is not set
New value: CONFIG_INPUT_KEYBOARD=y

Merging ./arch/x86/configs/qemu-busybox-min.config
Value of CONFIG_64BIT is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_64BIT is not set
New value: CONFIG_64BIT=y

Value of CONFIG_ACPI is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_ACPI is not set
New value: CONFIG_ACPI=y

Value of CONFIG_SERIAL_8250 is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_SERIAL_8250 is not set
New value: CONFIG_SERIAL_8250=y

Value of CONFIG_HYPERVISOR_GUEST is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_HYPERVISOR_GUEST is not set
New value: CONFIG_HYPERVISOR_GUEST=y

Value of CONFIG_CMDLINE_BOOL is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_CMDLINE_BOOL is not set
New value: CONFIG_CMDLINE_BOOL=y

#
# merged configuration written to .config (needs make)
#
#
# configuration written to .config
#
Using .config as base
Merging ./kernel/configs/rust.config
#
# merged configuration written to .config (needs make)
#
#
# configuration written to .config
#
$ make LLVM=1 allnoconfig qemu-busybox-min.config rust.config
#
# configuration written to .config
#
Using .config as base
Merging ./kernel/configs/qemu-busybox-min.config
Value of CONFIG_SMP is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_SMP is not set
New value: CONFIG_SMP=y

Value of CONFIG_PRINTK_TIME is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_PRINTK_TIME is not set
New value: CONFIG_PRINTK_TIME=y

Value of CONFIG_PCI is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_PCI is not set
New value: CONFIG_PCI=y

Value of CONFIG_BLK_DEV_INITRD is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_BLK_DEV_INITRD is not set
New value: CONFIG_BLK_DEV_INITRD=y

Value of CONFIG_BINFMT_ELF is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_BINFMT_ELF is not set
New value: CONFIG_BINFMT_ELF=y

Value of CONFIG_DEVTMPFS is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_DEVTMPFS is not set
New value: CONFIG_DEVTMPFS=y

Value of CONFIG_NET is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_NET is not set
New value: CONFIG_NET=y

Value of CONFIG_DEBUG_KERNEL is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_DEBUG_KERNEL is not set
New value: CONFIG_DEBUG_KERNEL=y

Value of CONFIG_INPUT_EVDEV is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_INPUT_EVDEV is not set
New value: CONFIG_INPUT_EVDEV=y

Value of CONFIG_INPUT_KEYBOARD is redefined by fragment ./kernel/configs/qemu-busybox-min.config:
Previous value: # CONFIG_INPUT_KEYBOARD is not set
New value: CONFIG_INPUT_KEYBOARD=y

Merging ./arch/x86/configs/qemu-busybox-min.config
Value of CONFIG_64BIT is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_64BIT is not set
New value: CONFIG_64BIT=y

Value of CONFIG_ACPI is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_ACPI is not set
New value: CONFIG_ACPI=y

Value of CONFIG_SERIAL_8250 is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_SERIAL_8250 is not set
New value: CONFIG_SERIAL_8250=y

Value of CONFIG_HYPERVISOR_GUEST is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_HYPERVISOR_GUEST is not set
New value: CONFIG_HYPERVISOR_GUEST=y

Value of CONFIG_CMDLINE_BOOL is redefined by fragment ./arch/x86/configs/qemu-busybox-min.config:
Previous value: # CONFIG_CMDLINE_BOOL is not set
New value: CONFIG_CMDLINE_BOOL=y

#
# merged configuration written to .config (needs make)
#
#
# configuration written to .config
#
Using .config as base
Merging ./kernel/configs/rust.config
Value of CONFIG_RUST is redefined by fragment ./kernel/configs/rust.config:
Previous value: # CONFIG_RUST is not set
New value: CONFIG_RUST=y

#
# merged configuration written to .config (needs make)
#
#
# configuration written to .config
#

重点的是我们看到 New value: CONFIG_RUST=y,这就是我们想要的,现在就可以编译成功具有 Rust 支持的内核版本了。

$ make LLVM=1 -j8
  SYNC    include/config/auto.conf.cmd
  SYSHDR  arch/x86/include/generated/uapi/asm/unistd_32.h
  SYSHDR  arch/x86/include/generated/uapi/asm/unistd_64.h
  SYSHDR  arch/x86/include/generated/uapi/asm/unistd_x32.h
  SYSTBL  arch/x86/include/generated/asm/syscalls_32.h
  SYSHDR  arch/x86/include/generated/asm/unistd_32_ia32.h
  SYSHDR  arch/x86/include/generated/asm/unistd_64_x32.h
  SYSTBL  arch/x86/include/generated/asm/syscalls_64.h
  WRAP    arch/x86/include/generated/uapi/asm/bpf_perf_event.h
  WRAP    arch/x86/include/generated/uapi/asm/errno.h
  WRAP    arch/x86/include/generated/uapi/asm/fcntl.h
  WRAP    arch/x86/include/generated/uapi/asm/ioctls.h
  WRAP    arch/x86/include/generated/uapi/asm/ipcbuf.h
  WRAP    arch/x86/include/generated/uapi/asm/ioctl.h
  WRAP    arch/x86/include/generated/uapi/asm/param.h
  WRAP    arch/x86/include/generated/uapi/asm/poll.h
  WRAP    arch/x86/include/generated/uapi/asm/resource.h
  WRAP    arch/x86/include/generated/uapi/asm/socket.h
  WRAP    arch/x86/include/generated/uapi/asm/sockios.h
  WRAP    arch/x86/include/generated/uapi/asm/termbits.h
  WRAP    arch/x86/include/generated/uapi/asm/termios.h
  WRAP    arch/x86/include/generated/uapi/asm/types.h
  HOSTCC  arch/x86/tools/relocs_32.o
  HOSTCC  arch/x86/tools/relocs_64.o
  HOSTCC  arch/x86/tools/relocs_common.o
  WRAP    arch/x86/include/generated/asm/early_ioremap.h
  WRAP    arch/x86/include/generated/asm/export.h
  WRAP    arch/x86/include/generated/asm/mcs_spinlock.h
  WRAP    arch/x86/include/generated/asm/irq_regs.h
  WRAP    arch/x86/include/generated/asm/kmap_size.h
  WRAP    arch/x86/include/generated/asm/local64.h
  WRAP    arch/x86/include/generated/asm/mmiowb.h
  WRAP    arch/x86/include/generated/asm/module.lds.h
  WRAP    arch/x86/include/generated/asm/rwonce.h
  WRAP    arch/x86/include/generated/asm/unaligned.h
  UPD     include/config/kernel.release
  UPD     include/generated/uapi/linux/version.h
  HOSTCC  scripts/kallsyms
  HOSTCC  scripts/sorttable
  HOSTRUSTC scripts/generate_rust_target
  UPD     include/generated/compile.h
  HOSTRUSTC scripts/rustdoc_test_builder
  UPD     include/generated/utsrelease.h
  HOSTRUSTC scripts/rustdoc_test_gen
  DESCEND objtool
  HOSTCC  /home/eli/GitHub/linux/tools/objtool/fixdep.o
  HOSTLD  /home/eli/GitHub/linux/tools/objtool/fixdep-in.o
  HOSTLD  arch/x86/tools/relocs
  LINK    /home/eli/GitHub/linux/tools/objtool/fixdep
  CC      /home/eli/GitHub/linux/tools/objtool/arch/x86/special.o
  CC      /home/eli/GitHub/linux/tools/objtool/check.o
  CC      /home/eli/GitHub/linux/tools/objtool/weak.o
  MKDIR   /home/eli/GitHub/linux/tools/objtool/arch/x86/lib/
  GEN     /home/eli/GitHub/linux/tools/objtool/arch/x86/lib/inat-tables.c
  CC      /home/eli/GitHub/linux/tools/objtool/exec-cmd.o
  CC      /home/eli/GitHub/linux/tools/objtool/special.o
  CC      /home/eli/GitHub/linux/tools/objtool/arch/x86/decode.o
  CC      /home/eli/GitHub/linux/tools/objtool/builtin-check.o
  CC      /home/eli/GitHub/linux/tools/objtool/help.o
  CC      /home/eli/GitHub/linux/tools/objtool/elf.o
  CC      /home/eli/GitHub/linux/tools/objtool/objtool.o
  CC      /home/eli/GitHub/linux/tools/objtool/orc_gen.o
  CC      /home/eli/GitHub/linux/tools/objtool/orc_dump.o
  CC      /home/eli/GitHub/linux/tools/objtool/libstring.o
  CC      /home/eli/GitHub/linux/tools/objtool/pager.o
  CC      /home/eli/GitHub/linux/tools/objtool/libctype.o
  CC      /home/eli/GitHub/linux/tools/objtool/parse-options.o
  CC      /home/eli/GitHub/linux/tools/objtool/run-command.o
  CC      /home/eli/GitHub/linux/tools/objtool/str_error_r.o
  CC      /home/eli/GitHub/linux/tools/objtool/librbtree.o
  CC      /home/eli/GitHub/linux/tools/objtool/sigchain.o
  CC      /home/eli/GitHub/linux/tools/objtool/subcmd-config.o
  CC      scripts/mod/empty.o
  HOSTCC  scripts/mod/mk_elfconfig
  CC      scripts/mod/devicetable-offsets.s
  LD      /home/eli/GitHub/linux/tools/objtool/arch/x86/objtool-in.o
  MKELF   scripts/mod/elfconfig.h
  HOSTCC  scripts/mod/modpost.o
  HOSTCC  scripts/mod/sumversion.o
  UPD     scripts/mod/devicetable-offsets.h
  HOSTCC  scripts/mod/file2alias.o
  LD      /home/eli/GitHub/linux/tools/objtool/libsubcmd-in.o
  AR      /home/eli/GitHub/linux/tools/objtool/libsubcmd.a
  HOSTLD  scripts/mod/modpost
  CC      kernel/bounds.s
  CHKSHA1 include/linux/atomic/atomic-arch-fallback.h
  CHKSHA1 include/linux/atomic/atomic-instrumented.h
  CHKSHA1 include/linux/atomic/atomic-long.h
  UPD     include/generated/timeconst.h
  UPD     include/generated/bounds.h
  CC      arch/x86/kernel/asm-offsets.s
  LD      /home/eli/GitHub/linux/tools/objtool/objtool-in.o
  LINK    /home/eli/GitHub/linux/tools/objtool/objtool
  UPD     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  GEN     scripts/gdb/linux/constants.py
  UPD     rust/target.json
  BINDGEN rust/bindings/bindings_generated.rs
  BINDGEN rust/bindings/bindings_helpers_generated.rs
  RUSTC L rust/core.o

# 此处略去 1000 行

Kernel: arch/x86/boot/bzImage is ready  (#1)

每次编译成功后就会有对应的编号生成,这里生成了 #1 的编号。

5. 编译 Rust 的 Echo Server 例子

为了验证,使用 RustEcho Server 例子。为了能在启动的时候看到输出,我们先修改一下代码。在 samples/rust 目录下,修改 rust_echo_server.rs 文件增加一行打印 pr_info!("Hello from echo server\n");

impl kernel::Module for RustEchoServer {
    fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
        pr_info!("Hello from echo server\n");
        let handle = WqExecutor::try_new(kernel::workqueue::system())?;
        start_listener(handle.executor())?;
        Ok(Self {
            _handle: handle.into(),
        })
    }
}

然后我们把 rust_echo_server 编译成内核模块。

$ make LLVM=1 menuconfig

busy-box-menuconfig-2

busy-box-menuconfig-2

busy-box-menuconfig-2

busy-box-menuconfig-2

busy-box-menuconfig-2

再次尝试编译带有 Rust 支持和 Echo Server 例子的内核。

$ make LLVM=1 -j8
  SYNC    include/config/auto.conf.cmd
  DESCEND objtool
  CALL    scripts/checksyscalls.sh
  AR      samples/vfio-mdev/built-in.a
  RUSTC     samples/rust/rust_echo_server.o
  AR      samples/rust/built-in.a
  AR      samples/built-in.a
  AR      built-in.a
  AR      vmlinux.a
  LD      vmlinux.o
  OBJCOPY modules.builtin.modinfo
  GEN     modules.builtin
  MODPOST vmlinux.symvers
  UPD     include/generated/utsversion.h
  CC      init/version-timestamp.o
  LD      .tmp_vmlinux.kallsyms1
ld.lld: warning: <internal>:(.eh_frame) is being placed in '.eh_frame'
  NM      .tmp_vmlinux.kallsyms1.syms
  KSYMS   .tmp_vmlinux.kallsyms1.S
  AS      .tmp_vmlinux.kallsyms1.S
  LD      .tmp_vmlinux.kallsyms2
ld.lld: warning: <internal>:(.eh_frame) is being placed in '.eh_frame'
  NM      .tmp_vmlinux.kallsyms2.syms
  KSYMS   .tmp_vmlinux.kallsyms2.S
  AS      .tmp_vmlinux.kallsyms2.S
  LD      vmlinux
ld.lld: warning: <internal>:(.eh_frame) is being placed in '.eh_frame'
  NM      System.map
  SORTTAB vmlinux
  CC      arch/x86/boot/version.o
  VOFFSET arch/x86/boot/compressed/../voffset.h
  OBJCOPY arch/x86/boot/compressed/vmlinux.bin
  GZIP    arch/x86/boot/compressed/vmlinux.bin.gz
  CC      arch/x86/boot/compressed/misc.o
  MKPIGGY arch/x86/boot/compressed/piggy.S
  AS      arch/x86/boot/compressed/piggy.o
  LD      arch/x86/boot/compressed/vmlinux
  ZOFFSET arch/x86/boot/zoffset.h
  OBJCOPY arch/x86/boot/vmlinux.bin
  AS      arch/x86/boot/header.o
  LD      arch/x86/boot/setup.elf
  OBJCOPY arch/x86/boot/setup.bin
  BUILD   arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready  (#2)

编译成功了,这里生成了 #2 的编号。

6. 准备 Busybox 的环境

为了能够在内核中运行 RustEcho Server 例子,我们需要准备一个 Busybox 的环境。 教学视频中对 Busybox 做了几次修改,这里为了简单起见,我直接跳到最后的配置版本。

$ cd busybox/__install
$ mkdir etc
$ cp ../examples/inittab /etc/
$ ls
bin  etc  linuxrc  sbin  usr
$ ls etc/inittab
etc/inittab
$ vim etc/inittab

这里注释掉 inittabtty 相关的代码。

# Start an "askfirst" shell on /dev/tty2-4
# tty2::askfirst:-/bin/sh
# tty3::askfirst:-/bin/sh
# tty4::askfirst:-/bin/sh

# /sbin/getty invocations for selected ttys
# tty4::respawn:/sbin/getty 38400 tty5
# tty5::respawn:/sbin/getty 38400 tty6

配置启动执行的脚本

$ mkdir etc/init.d
$ vim etc/init.d/rcS
mkdir -p /proc
mount -t proc none /proc
ifconfig lo up
udhcpc -i eth0
mkdir -p /dev
mount -t devtmpfs none /dev
mkdir -p /dev/pts
mount -t devpts none /dev/pts

telnetd -l /bin/sh
$ chmod a+x etc/init.d/rcS

准备 DHCP 服务的启动文件。

$ mkdir -p usr/share/udhcpc
$ cp ../examples/udhcp/simple.script usr/share/udhcpc/default.script
$ cat usr/share/udhcpc/default.script
#!/bin/sh
# udhcpc script edited by Tim Riker <Tim@Rikers.org>

RESOLV_CONF="/etc/resolv.conf"

[ -n "$1" ] || { echo "Error: should be called from udhcpc"; exit 1; }

NETMASK=""
if command -v ip >/dev/null; then
	[ -n "$subnet" ] && NETMASK="/$subnet"
else
	[ -n "$subnet" ] && NETMASK="netmask $subnet"
fi
BROADCAST="broadcast +"
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"

case "$1" in
	deconfig)
		echo "Clearing IP addresses on $interface, upping it"
		if command -v ip >/dev/null; then
			ip -4 addr flush dev $interface
			ip link set dev $interface up
		else
			ifconfig $interface 0.0.0.0
		fi
		;;

	renew|bound)
		echo "Setting IP address $ip on $interface"
		if command -v ip >/dev/null; then
			ip addr add $ip$NETMASK $BROADCAST dev $interface
		else
			ifconfig $interface $ip $NETMASK $BROADCAST
		fi

		if [ -n "$router" ] ; then
			echo "Deleting routers"
			while route del default gw 0.0.0.0 dev $interface ; do
				:
			done

			metric=0
			for i in $router ; do
				echo "Adding router $i"
				if [ "$subnet" = "255.255.255.255" ]; then
	# special case for /32 subnets:
	# /32 instructs kernel to always use routing for all outgoing packets
	# (they can never be sent to local subnet - there is no local subnet for /32).
	# Used in datacenters, avoids the need for private ip-addresses between two hops.
					ip route add $i dev $interface
				fi
				route add default gw $i dev $interface metric $((metric++))
			done
		fi

		# If the file is a symlink somewhere (like /etc/resolv.conf
		# pointing to /run/resolv.conf), make sure things work.
		if test -L "$RESOLV_CONF"; then
			# If it's a dangling symlink, try to create the target.
			test -e "$RESOLV_CONF" || touch "$RESOLV_CONF"
		fi
		realconf=$(readlink -f "$RESOLV_CONF" 2>/dev/null || echo "$RESOLV_CONF")
		echo "Recreating $realconf"
		tmpfile="$realconf-$$"
		> "$tmpfile"
		[ -n "$domain" ] && echo "search $domain" >> "$tmpfile"
		for i in $dns ; do
			echo " Adding DNS server $i"
			echo "nameserver $i" >> "$tmpfile"
		done
		mv "$tmpfile" "$realconf"
		;;
esac

exit 0

压缩 Busybox 的文件系统为镜像文件。


$ find . | cpio -H newc -o | gzip > ../ramdisk.img
4937

7. 使用 QEMU 和 Rust 支持的内核启动 Busybox 镜像

进入到 linux 目录下,使用 QEMU 启动 Rust 支持的内核。启动的时候,把虚拟机的 23 端口映射到宿主机的 5555 端口,这样我们就可以通过 telnet 连接到虚拟机的 23 端口;把虚拟机的 8080 端口映射到宿主机的 5556 端口,这样可以使用 nc 命令连接到虚拟机的 Echo Server 服务。

$ cd linux
$ qemu-system-x86_64 -nographic -kernel vmlinux -initrd ../busybox/ramdisk.img -nic user,model=rtl8139,hostfwd=tcp::5555-:23,hostfwd=tcp::5556-:8080

SeaBIOS (version 1.16.0-debian-1.16.0-4)

iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8B0A0+07ECB0A0 CA00

Booting from ROM..[    0.000000] Linux version 6.1.0-rc1+ (eli@eli-MI) (Ubuntu clang version 15.0.2-1, Ubuntu LLD 15.0.2) #3 SMP Wed Nov 16 20:00:20 CST 2022
[    0.000000] Command line:
[    0.000000] x86/fpu: x87 FPU will use FXSAVE
[    0.000000] signal: max sigframe size: 1040
[    0.000000] BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009fc00-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x0000000007fdffff] usable
[    0.000000] BIOS-e820: [mem 0x0000000007fe0000-0x0000000007ffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[    0.000000] NX (Execute Disable) protection: active
[    0.000000] SMBIOS 2.8 present.
[    0.000000] DMI: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.0-debian-1.16.0-4 04/01/2014
[    0.000000] tsc: Fast TSC calibration using PIT
[    0.000000] tsc: Detected 1992.228 MHz processor

# 此处略去 1000 行

[    0.689060] serio: i8042 AUX port at 0x60,0x64 irq 12
[    0.697680] rust_echo_server: Hello from echo server
[    0.701431] NET: Registered PF_INET6 protocol family
[    0.704861] input: AT Translated Set 2 keyboard as /devices/platform/i8042/serio0/input/input1
[    0.721321] Segment Routing with IPv6
[    0.721716] In-situ OAM (IOAM) with IPv6
[    0.722419] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
[    0.724941] NET: Registered PF_PACKET protocol family
[    0.725245] IPI shorthand broadcast: enabled
[    0.725705] sched_clock: Marking stable (686569475, 38062780)->(728696119, -4063864)
[    0.732047] Freeing initrd memory: 1272K
[    0.769075] Freeing unused kernel image (initmem) memory: 932K
[    0.773170] Write protecting the kernel read-only data: 12288k
[    0.775963] Freeing unused kernel image (text/rodata gap) memory: 2044K
[    0.777177] Freeing unused kernel image (rodata/data gap) memory: 792K
[    0.777681] Run /sbin/init as init process

Please press Enter to activate this console. [    1.605742] tsc: Refined TSC clocksource calibration: 1992.216 MHz
[    1.607809] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x396ee9d8a25, max_idle_ns: 881590455116 ns
[    1.610399] clocksource: Switched to clocksource tsc

在启动的过程中我们看到了 激动人心 的输出,RustEcho Server 已经启动了。

[    0.697680] rust_echo_server: Hello from echo server

让我们看看虚拟机中的网络情况。

# ifconfig
eth0      Link encap:Ethernet  HWaddr 52:54:00:12:34:56
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fec0::5054:ff:fe12:3456/64 Scope:Site
          inet6 addr: fe80::5054:ff:fe12:3456/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:3 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:1248 (1.2 KiB)  TX bytes:1286 (1.2 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

~ # wget www.google.com
Connecting to www.google.com (142.250.72.4:80)
Connecting to www.google.com.hk (142.250.72.67:80)
Connecting to www.google.com.hk (142.250.72.67:80)
saving to 'index.html'
index.html           100% |********************************| 14902  0:00:00 ETA
'index.html' saved
~ # rm index.html

在虚拟机中,命令提示符已经是 # ,便于和外部的命令行区分。

从虚拟机内部连接到 Echo Server 试试。

~ # netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 :::23                   :::*                    LISTEN
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path
~ # nc 127.0.0.1 8080
Hello,
Hello,
World!
World!
Rust for Linux
Rust for Linux
^Cpunt!

新开一个 Terminal ,从虚拟机外部的宿主机连接进去看看。

$ nc localhost 5556
Hello,
Hello,
World!
World!
Rust for Linux
Rust for Linux

这时候我们在虚拟机内看一下网络的状态。

~ # netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 10.0.2.15:8080          10.0.2.2:51558          ESTABLISHED
tcp        0      0 :::23                   :::*                    LISTEN
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path

可以试试使用 telenet 命令连接到虚拟机看是否正常。

$ telnet localhost 5555
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

~ # ps
PID   USER     TIME  COMMAND
    1 0         0:00 init
    2 0         0:00 [kthreadd]
    3 0         0:00 [rcu_gp]
    4 0         0:00 [rcu_par_gp]
    5 0         0:00 [slub_flushwq]
    6 0         0:00 [netns]
    7 0         0:00 [kworker/0:0-rcu]
    8 0         0:00 [kworker/0:0H-ev]
    9 0         0:00 [kworker/u2:0-ev]
   10 0         0:00 [mm_percpu_wq]
   11 0         0:00 [ksoftirqd/0]
   12 0         0:00 [rcu_sched]
   13 0         0:00 [migration/0]
   14 0         0:00 [cpuhp/0]
   15 0         0:00 [kdevtmpfs]
   16 0         0:00 [inet_frag_wq]
   17 0         0:00 [kworker/0:1-eve]
   18 0         0:00 [oom_reaper]
   19 0         0:00 [writeback]
   20 0         0:00 [kblockd]
   21 0         0:00 [kswapd0]
   22 0         0:00 [kworker/0:1H]
   23 0         0:00 [kworker/u2:1-ev]
   24 0         0:00 [acpi_thermal_pm]
   25 0         0:00 [kworker/u2:2]
   26 0         0:00 [mld]
   27 0         0:00 [kworker/0:2-eve]
   28 0         0:00 [ipv6_addrconf]
   44 0         0:00 udhcpc -i eth0
   45 0         0:00 -/bin/sh
   51 0         0:00 telnetd -l /bin/sh
   53 0         0:00 /bin/sh
   54 0         0:00 ps
~ # ls
bin          dev          etc          linuxrc      proc         ramdisk.img  root         sbin         usr
~ # uname
Linux
~ # uname -a
Linux (none) 6.1.0-rc1+ #3 SMP Wed Nov 16 20:00:20 CST 2022 x86_64 GNU/Linux
~ # kill -9 45

这时候我们发现虚拟机的 bash 已经离线,需要通过 Enter 后重新 attch。大家可以试试在 telnet 中执行 poweroff 会怎么样,哈哈哈~~

8. 总结

目前这个环境可以支持使用 Rust 开发 Linux Kernel ,使用 Busybox 启动进行验证,后续的笔记开始分析 Echo Server 的代码,以及 sample 目录下的其它代码。

当然,还是强烈建议跟视频一起做一遍,这样对环境搭建的理解会更加深入。

9. 参考资料

[1] Rust for Linux

[2] Mentorship Session: Setting Up an Environment for Writing Linux Kernel Modules in Rust

[3] Rust for Linux - Quick Start