从零开始的 rust 内核开发 (二)

本贴最后更新于 300 天前,其中的信息可能已经事过景迁

之前已经构建了一个内核,并且运行测试通过,不过在我的机器上,需要 30 分钟编译整个过程。这个太久了。考虑到可能会时常编译内核。

所以我打算换一个最小化内核的方式,当然这个内核是不能直接给系统用的,只能用作开发环境。

其实这个过程中最大的问题在于网络与共享,内核是最小化编译的,所以没有带任何网络驱动。好在内核自带了 kvm_config。一键带上 virtio 相关的所有驱动。然后第二个问题就是共享文件,因为我需要编译后扔到 qemu 里面去。

其实一开始想得是,每次都打个 initramfs。但是想想还是太麻烦了,灵光一闪想到 NFS。结果试了一下 busybox 里面死活挂不上 nfs。搞了几个小时,最后发现是配置写错了。(我特么真想抽死自己)

改了配置发现还是挂不上,才发现还有防火墙和 SELinux 的问题。最后都改完,发现还是挂不上。ORZ...

应该是 busybox 编的有问题,实在不想再去搞这个问题,然后再想想内核带了 nfsroot 参数。回头带上这个参数重新编译了一次。结果每次都卡死在初始化。走投无路去搜了一下,才发现要指明 nfsvers 版本号。

然后还有一个问题就是 kernel space 是 LLVM 编译的,user space 是 gcc 编译的。

然后然后,我试了一下 ban 掉 SELinux,然后系统就起不来了。索性,我就直接回滚快照。没有去找为什么。

然后把之前的步骤重新归纳整理一下。所以下面的内容比较流水帐。

跟着上一篇的内容,编译了 6.3.9 的版本的内核。

最小化内核编译

解压内核

mkdir -p ~/workspace/sources
tar -xvf linux-6.3.9.tar.xz -C ~/workspace/sources
cd ~/workspace/sources/linux-6.3.9

编译最小化内核

mkdir build
make LLVM=1 LLVM_IAS=1 O=build mrproper
make LLVM=1 LLVM_IAS=1 O=build tinyconfig
make LLVM=1 LLVM_IAS=1 O=build kvm_guest.config

这一步会让内核自己根据当前的情况,获取最小化的内核配置。

cd build
make LLVM=1 LLVM_IAS=1 menuconfig

基础配置

64-bit kernel ---> yes
# initrd支持
General setup ---> Initial RAM filesystem and RAM disk (initramfs/initrd) support ---> yes
General setup ---> Configure standard kernel features ---> Enable support for printk ---> yes
# insmod/rmmod/modprobe支持
Enable loadable module support --> yes
Enable loadable module support --> Forced module loading --> yes
Enable loadable module support --> Module unloading --> yes
Executable file formats / Emulations ---> Kernel support for ELF binaries ---> yes
Executable file formats / Emulations ---> Kernel support for scripts starting with #! ---> yes
Executable file formats / Emulations ---> Enable core dump support ---> yes
# /dev文件系统支持
Device Drivers ---> Generic Driver Options ---> Maintain a devtmpfs filesystem to mount at /dev ---> yes
Device Drivers ---> Generic Driver Options ---> Automount devtmpfs at /dev, after the kernel mounted the rootfs ---> yes
Device Drivers ---> Generic Driver Options ---> Use nosuid,noexec mount options on devtmpfs---> yes
# tty支持
Device Drivers ---> Character devices ---> Enable TTY ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> 8250/16550 and compatible serial support ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> Console on 8250/16550 and compatible serial port ---> yes
# 基础文件系统支持
File systems ---> Pseudo filesystems ---> /proc file system support ---> yes
File systems ---> Pseudo filesystems ---> sysfs file system support ---> yes
File systems ---> Enable POSIX file locking API --> yes

NFS 配置

General setup ---> Auditing support ---> yes
General setup ---> Configure standard kernel feature ---> Multiple users, groups and capabilities support yes
File systems ---> Network File Systems ---> NFS client support ---> yes
File systems ---> Network File Systems ---> NFS client support for NFS version 2 ---> yes
File systems ---> Network File Systems ---> NFS client support for NFS version 3 ---> yes
File systems ---> Network File Systems ---> NFS client support for NFS version 4 ---> yes
File systems ---> Network File Systems ---> NFS client support for NFSv4.1 ---> yes
File systems ---> Network File Systems ---> NFS client support for NFSv4.2 ---> yes
File systems ---> Network File Systems ---> root file system on NFS ---> yes
File systems ---> Network File Systems ---> NFS: Disable NFS UDP protocol suppor ---> yes

DEBUG 配置

Kernel hacking ---> x86 Debugging ---> Choose kernel unwinder ---> Frame pointer unwinder ---> yes
Kernel hacking ---> Compile-time checks and compiler options ---> Debug information ---> Generate DWARF Version 5 debuginfo ---> yes
Kernel hacking ---> Compile-time checks and compiler options ---> Provide GDB scripts for kernel debugging ---> yes
Kernel hacking ---> Generic Kernel Debugging Instruments ---> Debug Filesystem ---> yes
Kernel hacking ---> Generic Kernel Debugging Instruments ---> Magic SysRq key ---> yes
Kernel hacking ---> Generic Kernel Debugging Instruments ---> Magic SysRq key ---> Enable magic SysRq key over serial ---> yes
Kernel hacking ---> Generic Kernel Debugging Instruments ---> KGDB: kernel debugger ---> KGDB_KDB: include kdb frontend for kgdb -> yes
Kernel hacking ---> Generic Kernel Debugging Instruments ---> KGDB: kernel debugger ---> KGDB: use kgdb over the serial console ---> yes
Kernel hacking ---> Generic Kernel Debugging Instruments ---> KGDB: kernel debugger ---> KGDB_KDB: keyboard as input device ---> yes

Rust 配置

General setup ---> Rust support ---> yes
Kernel hacking ---> Sample kernel code ---> Rust samples ---> Host programs ---> yes
Kernel hacking ---> Sample kernel code ---> Rust samples ---> Minimal ---> M
Kernel hacking ---> Sample kernel code ---> Rust samples ---> Printing macros ---> M

开始编译

make LLVM=1 LLVM_IAS=1 -j$(nproc) bzImage
make LLVM=1 LLVM_IAS=1 -j$(nproc) modules

打包

mkdir ~/workspace/{boot,rootfs}
cp arch/$(uname -m)/boot/bzImage ~/workspace/boot/vmlinuz
make INSTALL_MOD_PATH=~/workspace/rootfs modules_install mod-fw=

编译 Busybox

cd ~/sources
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1/
mkdir build
make O=build defconfig
cd build
make menuconfig
Settings ---> Build Options ---> Build BusyBox as a static binary (no shared libs) ---> yes
make -j$(nproc)
make install

构建 initrd

初始化目录结构

cd ~/workspace/rootfs
mkdir -p bin sbin etc proc sys dev usr/bin usr/sbin
cp -a ~/workspace/sources/busybox-*/build/_install/* .

插入初始化脚本 init

编辑 ~/workspace/rootfs/etc/init.d/rcS,并赋予执行权限 chmod +x

#!/bin/sh
sleep 3
mount -t proc none /proc
mount -t sysfs none /sys

打包生成 initramfs(可以省略)

find . -print0 | cpio --null -ov --format=newc \
    | gzip -9 > ../boot/initramfs.cpio.gz
  • 大部分内容来自 Gist @chrisdone
  • 在此基础之上新增了 NFS 网络支持与 qemu 相关支持

编译 Rust 驱动

cd ~/workspace/rust-out-of-tree-module/
git clean -xdf
make KDIR=~/workspace/sources/linux-6.3.9/build/ LLVM=1
make -C /home/vagrant/workspace/sources/linux-6.3.9/build/ M=$PWD
make[1]: Entering directory '/home/vagrant/workspace/sources/linux-6.3.9/build'
  RUSTC [M] /home/vagrant/workspace/rust-out-of-tree-module/rust_out_of_tree.o
make[2]: Warning: File '/home/vagrant/workspace/rust-out-of-tree-module/modules.order' has modification time 0.15 s in the future
  MODPOST /home/vagrant/workspace/rust-out-of-tree-module/Module.symvers
make[2]: warning:  Clock skew detected.  Your build may be incomplete.
make[2]: Warning: File '/home/vagrant/workspace/rust-out-of-tree-module/rust_out_of_tree.o' has modification time 0.11 s in the future
  CC [M]  /home/vagrant/workspace/rust-out-of-tree-module/rust_out_of_tree.mod.o
  LD [M]  /home/vagrant/workspace/rust-out-of-tree-module/rust_out_of_tree.ko
make[2]: warning:  Clock skew detected.  Your build may be incomplete.
make[1]: Leaving directory '/home/vagrant/workspace/sources/linux-6.3.9/build'

cp -v rust_out_of_tree.ko ~/workspace/rootfs/lib/modules/6.3.9/kernel/rust_out_of_tree.ko
'rust_out_of_tree.ko' -> '/home/vagrant/workspace/rootfs/lib/modules/6.3.9/kernel/rust_out_of_tree.ko'

共享目录启动

由于是开发环境所以需要需要和 kvm 主机共享目录,这里选择使用 nfs 直接把 rootfs 作为 qemu 的根

开启 nfs 服务器

sudo dnf install -y nfs-utils
sudo systemctl enable --now nfs-server rpcbind
sudo firewall-cmd --add-service={nfs,nfs3,mountd,rpc-bind} --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
sudo setsebool -P nfs_export_all_rw 1 # 开放SELinux权限

编辑/etc/exports

/home/vagrant/workspace/rootfs *(rw,sync,no_subtree_check,all_squash,insecure,fsid=1) 

刷新共享

sudo exportfs -avr

开启 nfs 挂载

sudo dnf install -y qemu-kvm-core
host_ip=$(ip addr show eth0 | grep -oP "inet \K\d+\.\d+\.\d+\.\d+")
/usr/libexec/qemu-kvm -kernel ~/workspace/boot/vmlinuz \
    -smp 2 \
    -cpu host \
    -s -S \
    -m 512 \
    -machine q35 \
    -netdev user,id=nic0 -device virtio-net-pci,netdev=nic0,mac=52:54:98:76:54:32 \
    -nographic \
    -append "console=ttyS0 ip=dhcp root=/dev/nfs nfsroot=$host_ip:/home/vagrant/workspace/rootfs,nfsvers=4 rw"
  • netdev:这里的 netdev 是代表网卡,网卡的模式使用的是 users 模式,就是那种最常见的与主机共享网络的模式。id 是指明这个 netdev 叫啥。因为后面的 device 要与之绑定。netdevdevice 是成对出现的。

  • device:netdevdevice 是要成对出现的,否则配置不生效。所以这里 device 绑定 nic0 这个 netdev。第一个参数是指定的网卡类型,这里一般有 e1000 啥的,这里用的是最基本的 virtio-net-pci。最后一个是 mac 地址,这个 qemu 不会给你生成。要自己写一个。

    echo "52:54:$(dd if=/dev/urandom count=1 2>/dev/null |md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4/')"
    
  • sS: 表示开放 gdb 调试服务,后续可以通过 target remote :1234 使用 gdb 调试,不进行调试则内核不执行。

  • mahince:q35,我不写 q35 它一直给我警告,说默认要 desperated 了。妈蛋,太吵了。

  • append:内核参数

    • console:这里指明 ttyS0 是因为 nographic 参数启动,会默认打在虚拟机的 console 口上。否则就没法看到内容。
    • ip: dhcp,用了 user 模式启动,自动获取 ip
    • root:标记使用 nfs 方式挂载根分区。没有一个 /dev/nfs 的设备符,这个只是个标记。
    • nfsroot: 指明目标 nfs 服务器的路径与地址。nfsvers 用以指明 nfs 服务器的版本,务必带上 nfsvers 参数,指明 nfs 服务器版本,否则有一定机率卡死。
    • rw:以读写的形式挂载内核

gdb 连接并启动

因为这玩意是开了 -S 的,gdb 上去,不 c 一下就一直卡在那

gdb vmlinux
Reading symbols from vmlinux...
(gdb) target remote :1234
Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0x000000000000fff0 in ?? ()
(gdb) c
Continuing.

回到 qemu,测试一下驱动

modprobe rust_out_of_tree
rust_out_of_tree: loading out-of-tree module taints kernel.
rust_out_of_tree: Rust out-of-tree sample (init)

rmmod rust_out_of_tree
rust_out_of_tree: My numbers are [72, 108, 200]
rust_out_of_tree: Rust out-of-tree sample (exit)

卡死在关机

测完了,poweroff 一下,卡死了,qemu 没有退出。gdb 显示卡死在 hlt 指令之后。

翻了半天,说是 gpio 的问题(好吧不知道这个是个啥)。带上相关配置,依然卡在关机。然后又翻到一个帖子说是 acpi 的问题,才想起来 tinyconfig 没有带上 acpi 配置。

最后附上,acpigpio 的配置

电源相关配置

Device Drivers ---> Device Tree and Open Firmware support ---> yes
Device Drivers ---> GPIO support ---> yes
Device Drivers ---> GPIO support ---> Virtual GPIO drivers ---> VirtIO GPIO support ---> yes
Device Drivers ---> Board level reset or power off ---> GPIO power-off driver ---> yes
Device Drivers ---> Board level reset or power off ---> GPIO restart driver ---> yes
Device Drivers ---> Board level reset or power off ---> Restart power-off driver ---> yes
Power management and ACPI options ---> ACPI (Advanced Configuration and Power Interface) Support ---> yes

带上新配置重新编译内核,poweroff -f 正常关机。

~~奇怪的是,为什么 poweroff 不能关机呢。感觉是 syscall 的问题,算了后面再说吧。~~搭个环境水了三篇。不出意外下一篇应该还是环境搭建,下次来配置一下 rust-analyser。然后简单看一下 Rust for linux 是如何工作的。

  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    57 引用 • 22 回帖 • 5 关注
  • 内核
    10 引用 • 14 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • ssfdust

    这里 poweroff 不关机是因为 services 中带了 exec /bin/sh,应当把这一行去掉。这样子 init 进程才能正常启动,加载正确的 shell。之后 poweroff 才能正常关机。而不必使用 poweroff -f

    ~/workspace/rootfs/etc/init.d/rcS 现在内容如下

    #!/bin/sh
    sleep 3
    mount -t proc none /proc
    mount -t sysfs none /sys