PXE 启动 archlinux
本文最后更新于 2024年11月17日 凌晨
最近想整个 PXE 启动的 archlinux 环境,启动之后像 archiso 一样,跑在内存里面。
实现思路
PXE 启动 grub, 然后走 http 下载 kernel 和 initramfs, 在 initramfs 启动过程中下载 rootfs,最后 switch_root
到 rootfs。
- 首先,我们需要制作一个可以启动的 initramfs 和 rootfs, 可以参考制作自定义的 archlinux initramfs 与 rootfs。此处不再赘述。
- 然后我们需要架设一个 http 服务器,因为我们的 grub 启动后,打算让它走 http 去加载内核 与 initramfs, 最后 initramfs 走 http 加载rootfs。可以用 darkhttp 或者 python 的 http.server 模块都行,此处不再赘述。
- 我们还需要架设一个 tftp 服务器,给 pxe 下发 grub 引导文件。具体分成三种情况,x86_64-efi, i386-pc,arm64-efi. 其中 arm64-efi 和 x86_64-efi 差不多,本文略过arm64-efi 的情况。(其实是没有机器用来测试)。
制作 grub 引导文件
i386-pc 的情况
首先是 i386-pc 的情况,先创建一个文件grub.cfg.pre.bios
,内容如下:
1 |
|
它的作用是告诉 grub
去 http 服务器上获取 grub.cfg
。然后执行下面的命令创建 grubpxe.0
, 其中 -c grub.cfg.pre.bios
会把我们刚刚创建的 grub.cfg.pre.bios
当成一个早期配置文件进行嵌入。然后执行下面的命令生成 pxe 引导文件 grubpxe.0
。 注意这里 -p ''
建议设置为空,不然会影响启动速度。
1 |
|
x86_64-efi 的情况
假设我们用 UEFI 的 PXE 启动方式,x86_64-efi 的情况下我们可以不使用 grub-mkimage
而是使用 grub-mkstandalone
。
先创建一个 grub.cfg.pre.uefi
文件。
1 |
|
然后生成引导文件 grubpxe.efi
1 |
|
然后执行下面的命令创建 grubpxe.efi
, 其中 --modules=
指定预加载的模块,--locales
指定包含的 locale, 建议只指定需要的,不然生成的产物会很大。你还可以通过指定 --fonts=''
来进一步减小大小(2M)。
当然,也可以使用 grub-mkimage
来制作引导文件。这种情况下需要写死 grub.cfg.pre.uefi
,原因暂时不明,可能漏了什么模块。
1 |
|
然后执行
1 |
|
HTTP BOOT 的情况
同样先新建一个文件 grub.cfg.pre.http
, 注意这里我们手动执行了 net_dhcp
并且硬编码了一个 efinet0
。
1 |
|
然后执行下面的命令创建 grubpxe-http.efi
, 其中 --modules=
指定预加载的模块,--locales
指定包含的 locale, 建议只指定需要的 locale,不然生成的产物会很大。你还可以通过指定 --fonts=''
来进一步减小大小(2M)。
1 |
|
grub-mkimage
的版本
1 |
|
架设 dhcp 与 tftp 服务器。
本文选用了 dnsmasq, 你可以可选用 dhcpcd.
本文使用的配置文件如下。其实设置了三种启动方式,X86-64_EFI_HTTP (UEFI 的http boot), efi-x86_64 pxe 和 bios pxe. http boot的情况下,dhcp 服务器直接告诉 qemu 去拿 http://192.168.122.1/boot/grub/grubpxe-http.efi
这个文件启动。
1 |
|
资源目录结构
对下面的目录结构进行一个简单说明:
archiso/arch
是从 archlinux iso 解压的资源,未做任何更改(可以用自定义的initramfs 和rootfs 替换对应文件)。archiso/boot/grub
下面的文件由grub-mknetdir --net-directory=. --subdir=/boot/grub -d /usr/lib/grub/x86_64-efi
生成, UEFI 和 legacy 各一份。grubpxe.0
和grubpxe.efi
是由上面的命令生成,并复制到/tmp/x64/pxe
下面。
1 |
|
grub.cfg
文件
1 |
|
启动服务
dhcp 服务: 在/tmp/x64/pxe
执行 sudo dnsmasq -C dnsmasq.conf -d
HTTP服务: 在 /tmp/x64/pxe/archiso
执行 sudo python3 -m http.server 80
一些小技巧
- 可以在
grub.cfg.pre.*
中的开头加入debug=all
, 并在virtmanager 中给 qemu 虚拟机添加控制台和串口(输出到文件),就可以在本地看启动日志,根据 grub 的失败日志来进行调整。 - grub-mkimage 制作出来的引导文件不需要加载 /boot/grub/下面的其他文件(i386-pc, x86_64-efi文件夹下的mod),因此注意将需要打的mod 在mkimage 命令中打进去(除非你在grub.cfg 中显示insmod)。
- grub-mkstandalone 中需要添加部分模块(如
linux
configfile
) 到--modules=
参数中让它预加载,不然会通过http去资源目录下获取。
启动耗时比较
记录了从virt-manager 按下 reset 到启动完成,提示login的时间。均未开启 debug
- uefi+http boot + mkimage: 38.83s
- uefi+http boot + mkstandalone: 38.6s.
- uefi+ mkimage+ 单文件 38.88s
- uefi+ mkstandalone: 38.70s
- bios+ mkimage+ 单文件(无i386-pc文件夹):25.32s
- bios+ mkimage+ 多文件(有i386-pc文件夹,有无不影响启动):25.25s (和单文件其实没差异)
结语
本文提出了一种通过 pxe/http 引导grub来启动内存系统的方案,该方案除了拉取 grub 核心引导文件是通过 tftp 以外,后续所有请求都是通过 http 完成,避免了 tftp 丢包的问题。但是本文也存在以下待进一步完善的问题:
- UEFI情况下
grub.cfg.pre.*
硬编码了efinet0
, 应该用$net_default_interface
替代。 - UEFI HTTP BOOT 的情况下,需要手动
net_dhcp
否则不会自动加载网络,如果能成功加载网络,则grub.cfg.pre.http
只需要写一行configfile ${cmdpath}/grub.cfg
即可,我之前成功过,但是目前无法复现。 - 我们在使用 grub-mkimage 的时候会将一行模块打到 grub 引导文件中,这会导致引导文件变大,从而拖慢启动速度,但是不包含进去又会导致从http 服务器下载,也会拖慢启动速度,本文并没有进一步比较两种方案的好坏并找到一个建议的方案。