制作自定义的 archlinux initramfs 与 rootfs

本文最后更新于 2024年3月30日 晚上

最近想做一个自定义的 archlinux pxe 启动环境,由于 pxe 走的 tftp, 因此希望 initramfs 尽量小,官方 iso 里面的 initramfs 带了一些 glibc 的动态链接库,因此体积还是比较大的。我打算基于 busybox 去做一个 initramfs, 尽量只包含 busybox 以及网卡驱动,其他一概不加,都放在 rootfs 里面。

最后所有脚本放在 Github 仓库

制作 initramfs

我并不打算从头写 init 脚本,而是尽量抄 mkinitcpio 的代码。首先我们尝试通过自制的 initramfs 引导 archiso 带的 rootfs.
先新建一个文件夹,将 archiso 的 initramfs 复制进去解压

1
lsinitcpio -x initramfs-linux.img

看一下目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
➜  initramfs tree -L 2
.
├── bin -> usr/bin
├── buildconfig
├── codesign.crt
├── config
├── dev
├── etc
│   ├── ca-certificates
│   ├── fstab
│   ├── initrd-release -> ../usr/lib/os-release
│   ├── ld.so.cache
│   ├── ld.so.conf
│   ├── modprobe.d
│   ├── mtab -> ../proc/self/mounts
│   ├── nsswitch.conf
│   ├── os-release -> ../usr/lib/os-release
│   └── ssl
├── hooks
│   ├── archiso
│   ├── archiso_loop_mnt
│   ├── archiso_pxe_common
│   ├── archiso_pxe_http
│   ├── archiso_pxe_nbd
│   ├── archiso_pxe_nfs
│   ├── memdisk
│   └── udev
├── init
├── init_functions
├── lib -> usr/lib
├── lib64 -> usr/lib
├── new_root
├── proc
├── run
├── sbin -> usr/bin
├── sys
├── tmp
├── usr
│   ├── bin
│   ├── lib
│   ├── lib64 -> lib
│   ├── local
│   └── sbin -> bin
├── var
│   └── run -> /run
└── VERSION

这几乎就是一个正常根目录的结构,其中 init 就是启动脚本,内核在解压 initramfs 后会执行 init. hooks/ 文件夹是 archiso 的各种 hook, 通过 config 配置,自己写的变更就可以放在 hook 里面。

主要的命令和库都放在 usr/binusr/lib 下面。我们先把这两个文件夹清空(因为我们尽量只用 busybox 嘛)。
同样的,那些 hook,
然后将 busybox 安装到 usr/bin

1
busybox --install -s ${INITRAMFS_DIR}/usr/bin

再把 busybox 本体复制过去。

1
install -Dm755 /usr/bin/busybox  -t ${INITRAMFS_DIR}/usr/bin

然后我们由于我们打算通过 http 传输 rootfs, 所以得加上网卡驱动。把内核自带的网卡驱动都复制到 usr/lib/modules 下面.
注意,这些驱动一定要是 .ko 格式(而不是 .ko.zst, 因为两次压缩没有必要)

同时由于我们要挂载 squashfs 形式的 rootfs 还要挂载 overlayfs, 因此也需要 squashfsoverlayfs 这两个模块。
另外,由于原来的 initramfs 使用了一些 glibc 的库和命令,它们与 busybox 的不一致,我们需要对脚本做一些修改以兼容 busybox 的命令, 比如 busyboxmount 没有 --mkdir,同时我们将 curl 更换为 wget

losetup 我不会配置参数以达到和原来一样的效果,因此这个就直接用glibclosetup 而不是 busybox 的了。我们需要将它以及它所依赖的动态库都一起复制过去,具体可以通过 lddtree 命令查看。这里 /usr/lib/libsmartcols.so.1 是一个软链接,所以我直接把 /usr/lib/libsmartcols.so* 都复制过去了。

1
2
3
4
➜  ~ lddtree /usr/bin/losetup
/usr/bin/losetup (interpreter => /lib64/ld-linux-x86-64.so.2)
libsmartcols.so.1 => /usr/lib/libsmartcols.so.1
libc.so.6 => /usr/lib/libc.so.6

由于我们使用的 busybox 和 原装 initramfs 的 busybox 不一致(我们使用的 busybox 会优先使用 busybox applet, 而不是 PATH 中的命令),因此,在使用两者都有的命令时,需要使用绝对路径(例如 /usr/bin/losetup).

另外我们加了一个 addmod 的 hook, 并且在 config 中先于其他 hook 执行以加载网卡(原来是在 udev 中执行的,但是我们把 udev 拿掉了)。

打包

linux 内核其实支持两种引导的格式,initrd 与 initramfs, 两者的区别可以看参考资料。我们使用如下的命令创建 initramfs。 其中 xz -1 是压缩程序及压缩等级,也可以调整成 zstd -19.

1
find . 2>/dev/null | cpio -o -R root:root -H newc | xz -1 --format=lzma >../initramfs-linux.img

启动

命令行启动的启动命令(方便查看日志调试)

1
2
3
4
5
6
7
8
qemu-system-x86_64 \
-m 18G \
-net nic -net user -nographic \
-device nec-usb-xhci,id=xhci,addr=0x1b \
-device usb-tablet,id=tablet,bus=xhci.0,port=1 \
-device usb-kbd,id=keyboard,bus=xhci.0,port=2 \
-smp 4,cores=4 -kernel vmlinuz-linux -initrd initramfs-linux.img 、
-append 'archisobasedir=arch archiso_http_srv=http://192.168.122.1/ ip=dhcp console=ttyS0 '

启动的同时我们要保证启动了一个 http 服务,并确保http://192.168.122.1/arch/x86_64/airootfs.sfs 这个路径有效,这样我们就能顺利启动了。

制作 rootfs

airootfs.sfs 可以自己制作. ${ROOTFS_DIR} 是一个与 initramfs 无关的文件夹, 同时我们将 root 密码设置成了 root

1
2
3
4
5
sudo pacstrap -K "${ROOTFS_DIR}" base # and other pkgs you like
sudo arch-chroot "${ROOTFS_DIR}" passwd<<EOF
root
root
EOF

接着使用如下的命令打包 squashfs, 打包出来的文件就是 airootfs.sfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo mksquashfs "${ROOTFS_DIR}" airootfs.sfs \
-not-reproducible \
-xattrs \
-wildcards \
-noappend \
-progress \
-mem 5G \
-e \
var/cache/pacman/pkg \
var/lib/pacman/sync \
var/log/journal \
efi \
boot/grub \
boot/initramfs-linux"*".img \
boot/vmlinuz-linux

暂未解决的问题

目前 losetupipconfig 还是用的动态链接的,尤其 ipconfig 是从源码编译的,后续可以看看如何用 busybox 的内置命令实现这两个功能,这样我们就完全不需要 glibc 的库了。(另一种方法是将 busybox 也换成动态链接的版本)

参考资料

  1. initrd-vs-initramfs
  2. Preboot Execution Environment
  3. PXE network boot methods

制作自定义的 archlinux initramfs 与 rootfs
https://blog.askk.cc/2024/03/30/custom-archlinux-initramfs-and-rootfs/
作者
sukanka
发布于
2024年3月30日
许可协议