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
当前我安装的是 Rust 的 1.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
。 在之前安装系统环境的时候我们就安装了 LLVM
和 clang
,但是所以这里不再提示相关的错误。
$ 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 的源代码来交叉编译 core
和 alloc
库。
$ 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!
如果提交 PR 给 Rust for Linux ,需要对代码进行格式化和 Clippy 检查,所以需要安装 rustfmt 和 clippy 组件。
$ rustup component add rustfmt
$ rustup component add clippy
3. 安装 Busybox 环境
光有 Rust 环境还不够,还需要安装 Busybox 环境。 Busybox 是一个非常小的 Linux 发行版,它包含了 Linux 系统中的大部分命令,比如 ls
、cat
、grep
等等。 调试环境的主要是利用 QEMU
把 Rust 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
执行 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 例子
为了验证,使用 Rust 的 Echo 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
再次尝试编译带有 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 的环境
为了能够在内核中运行 Rust 的 Echo 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
这里注释掉 inittab
中 tty
相关的代码。
# 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
在启动的过程中我们看到了 激动人心 的输出,Rust
的 Echo 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