目标
过程
我决定现在mac上进行这个实验,因为我目前不会进行图形化的自定义,只对内核和基本的用户空间自定义。所以理论上可以使用orbstack这一个轻量级的虚拟机软件
查询orbstack是否支持自定义的iso的时候,发现令人失望
ref: https://github.com/orbstack/orbstack/issues/11
因此还是只能使用一些比较重量级的方案
直接使用UTM吧,反正只是一个验证的方式
但是这有点风险,因为lfs的方式是直接在一个实机上的硬盘安装系统
我测,deepseek直接给我了一个更好的选择,qemu,这样直接不需要utm这种重量级的东西了
接下来的技术方案确定了,在mac上使用orbstack的container编译内核和用户空间,然后使用qemu运行,竟然有成功的先例
这下省心了
orbstack的使用->干净的构建环境
使用docker的时候没有找到合适的镜像,然后看了一些orbstack的vm,发现他可以模拟amd64,那么后续可以直接在这里直接进行amd64的编译,目前为了更快的编译,使用arm64的vm。
vm不需要特殊的配置,直接
orbctl create -a arm64 -u spriple arch
即可创建一个arch为arm64,默认用户为spriple的archlinux虚拟机
创建之后使用list来查看vm状态
> orbctl list
NAME STATE DISTRO VERSION ARCH SIZE IP
---- ----- ------ ------- ---- ---- --
arch running archlinux current arm64 1.1 GB 192.168.139.80
然后进入vm之中
kitty +kitten ssh arch@orb进入名为arch的虚拟机中
然后clone linux内核,选择一个比较稳定一点的版本
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
这是用时最久的一个部分,我网络不好,因此只想clone某一个版本的linux内核
git clone --depth 1 --branch v6.18 --single-branch https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
这下快多了,询问ai之后,竟然还可以使用国内的镜像源
尝试过清华源,出了问题,最终还是使用ustc源
git clone https://mirrors.ustc.edu.cn/linux.git
快的起飞
部署必要的构建工具
sudo pacman -Syu fish
先弄一个fish,来帮我们更快的补全命令,之后构建的时候大概率需要换回bash
然后安装构建linux内核所需要的工具
原本的apt系列的命令为
sudo apt install -y build-essential flex bison libssl-dev libelf-dev libncurses-dev qemu-system-aarch64 gdb dwarves busybox-static e2fsprogs bc
pacman系列为
sudo pacman -Syu base-devel openssl ncurses libelf bc python gdb
其中base-devel包括了flex、bison
openssl包括了libssl-dev
libelf
ncurses
构建内核
spriple@arch ~/b/linux ((v6.18))> make ARCH=arm64 defconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/confdata.o
HOSTCC scripts/kconfig/expr.o
LEX scripts/kconfig/lexer.lex.c
YACC scripts/kconfig/parser.tab.[ch]
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
*** Default configuration is based on 'defconfig'
#
# configuration written to .config
#
然后
make ARCH=arm64 menuconfig
关于menuconfig的众多选项,我们可以不需要管太多,一般只需要关注比较重要的部分即可
- cpu类型为arm64还是x64
然后直接开始构建
make -j 8
然后使用一下命令检查是否构建出来了真东西
spriple@arch ~/b/linux ((v6.18))> file vmlinux
vmlinux: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), statically linked, BuildID[sha1]=8f13b2d3a7b73350ad0a739a9ebd4755b9d30199, with debug_info, not stripped
spriple@arch ~/b/linux ((v6.18))> file ./arch/arm64/boot/Image
./arch/arm64/boot/Image: Linux kernel ARM64 boot executable Image, little-endian, 4K pages
构建用户空间
然后开始构建用户空间,使用常用的busybox
sudo pacman -Syu busybox
然后确定下载的busybox是静态连接的
spriple@arch ~/b/rootfs> file /usr/bin/busybox
/usr/bin/busybox: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped
之后就可以复制这个命令
mkdir -p bin sbin etc proc sys dev lib usr/bin usr/sbin
cp /usr/bin/busybox ./bin/busybox
./bin/busybox --install -s ./bin
因为我们取巧在文件夹中安装的busybox,因此bin下面的全部命令都会被硬连接到绝对路径中(其实官方更推荐源码直接make)
spriple@arch ~/b/rootfs> file bin/init
bin/init: symbolic link to /home/spriple/build/rootfs/bin/busybox
但是我们后面会将这个根文件系统放在一个img镜像中,因此必须使用相对路径
所以我们使用一个脚本修复一下
#!/bin/bash
cd ~/build/rootfs
prefix="/home/spriple/build/rootfs"
find . -type l | while read link; do
target=$(readlink "$link")
# 如果目标以prefix开头
if [[ "$target" == "$prefix"* ]]; then
# 去掉前缀得到目标在rootfs中的绝对路径(相对于rootfs根)
newtarget="${target#$prefix}"
# 确保newtarget以/开头
# 计算从链接所在目录到newtarget的相对路径
linkdir=$(dirname "$link")
# 使用realpath工具计算相对路径,或者用python
relpath=$(realpath --relative-to="$linkdir" ".$newtarget" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "Fixing $link: $target -> $relpath"
ln -snf "$relpath" "$link"
else
# 如果没有realpath,尝试用awk计算
# 简单方法:如果newtarget就是/bin/busybox,且链接在bin目录下,relpath就是"busybox"
# 更通用的:用perl
relpath=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(".'"$newtarget"'", "'"$linkdir"'")')
ln -snf "$relpath" "$link"
fi
fi
done
然后chmod +x script.sh && bash script.sh
然后用命令检查一下是否生效
spriple@arch ~/b/rootfs> file bin/init
bin/init: symbolic link to busybox
然后需要定义一下启动的脚本
# 创建 init.d 目录
mkdir -p etc/init.d
# 创建 rcS 文件
cat <<EOF > etc/init.d/rcS
#!/bin/sh
# 挂载必要的伪文件系统
echo "Mounting /proc, /sys, /dev..."
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
# 打印一些启动信息
echo "============================="
echo " Minimal BusyBox RootFS Booted"
echo " Running rcS..."
echo " Remounting rootfs rw..."
echo "============================="
# 重新挂载根文件系统为可读写模式
# (虽然 QEMU append 参数里也加了 rw,但这里再确认一下)
mount -o remount,rw /
# 启动一个交互式 Shell
echo "Starting shell..."
exec /bin/sh
EOF
# 给 rcS 脚本添加执行权限
chmod +x etc/init.d/rcS
然后创建一个img文件,将根文件系统塞进去
dd if=/dev/zero of=rootfs.img bs=1M count=512
mkfs.ext4 rootfs.img
#创建挂载点
sudo mkdir -p /mnt/rootfs_mount
#挂载rootfs.img,方便操作
sudo mount -o loop rootfs.img /mnt/rootfs_mount
## 将我们定义的根文件系统的内容cp过去
sudo cp -a rootfs/* /mnt/rootfs_mount/
sync
sudo umount /mnt/rootfs_mount
sudo rmdir /mnt/rootfs_mount
然后rootfs.img里面就有了我们自定义的根文件系统了
然后使用qemu启动rootfs
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-m 1G \
-nographic \
-kernel build/linux/arch/arm64/boot/Image \
-drive file=rootfs.img,format=raw,if=virtio,id=hd0 \
-append "root=/dev/vda console=ttyAMA0 rw init=/bin/init"
然后即可看见启动日志和init脚本执行情况
[ 0.435950] Run /bin/init as init process
starting pid 55, tty '': '/etc/init.d/rcS'
Mounting /proc, /sys, /dev...
mount: mounting none on /dev failed: Resource busy
=============================
Minimal BusyBox RootFS Booted
Running rcS...
Remounting rootfs rw...
=============================
[ 0.501618] EXT4-fs (vda): re-mounted 130b414a-7366-4d19-a319-6f0b6c03aca2.
Starting shell...
/bin/sh: can't access tty; job control turned off
~ # ls
bin etc lost+found sbin usr
dev lib proc sys
~ # ps
PID USER TIME COMMAND
1 0 0:00 init
2 0 0:00 [kthreadd]
打包为iso文件
使用grub-mkrescue
sudo pacman -Syu grub xorriso mtools
首先按照ai的说法直接使用grub
grub-mkrescue --output=~/custom-linux.iso .
出现错误
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project. libburn : SORRY : Neither stdio-path nor its directory exist xorriso : FAILURE : Cannot acquire drive 'stdio:~/custom-linux.iso' xorriso : aborting : -abort_on 'FAILURE' encountered 'FAILURE' grub-mkrescue: error: `xorriso` invocation failed
询问ai得知,xorriso无法识别~,因此使用绝对路径
grub-mkrescue --output=/home/spriple/custom-linux.iso .
随后成功打包出iso文件
但是使用下面qemu命令测试的时候qemu没有反应
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -cdrom ~/custom-linux.iso -nographic
第一个问题就是对于arm64的内核,qemu没有对应的uefi固件,因此我们需要下载对应的uefi固件
sudo pacman -Syu edk2-armvirt
随后uefi固件就是被下载到/usr/share/edk2/aarch64/QEMU_EFI.fd
然后我们用下面的命令启动
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -bios /usr/share/edk2/aarch64/QEMU_EFI.fd -cdrom ~/custom-linux.iso -nographic
此时虽然qemu有反应了,但是内核panic
[ 0.558533] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on "/dev/vda" or unknown-block(254,0) ]---
询问ai得知为没有virtio磁盘设备导致的
这种问题呢,我稍微仔细问了一下
rootfs的创建方式决定了它是一个文件,需要一个块设备,也就是盘作为载体,才能挂载,所以我们之前使用qemu使用它的时候,会自动创建一个块设备挂载它,因此使用起来ok
但是我们把他打包进入iso文件之后,它仅仅是一个iso文件系统中的一个文件,qemu不会给它做特殊处理,此时如果想要用的话,需要弄一个块设备(需要补充实验)
但是initramfs借助的是内存
gemini:内核根本不知道如何在没有 initramfs 的协助下,直接从 ISO9660 文件系统里找到 /bin/init 并正确执行它作为根分区。
因此我决定使用initramfs,更加简洁方便
然后使用下面命令重新打包根文件系统
sudo pacman -Syu cpio
cd ~/build/rootfs
find . -print0 | cpio --null -o --format=newc | gzip -9 > ./initramfs.cpio.gz
然后将initramfs拷贝到iso_build的boot目录下面,并且重新编辑grub配置
cp ~/build/rootfs/initramfs.cpio.gz ~/build/iso_build/boot/
set default=0
set timeout=5
menuentry "My Custom Linux System" {
linux /boot/Image console=ttyAMA0 rdinit=/bin/init
initrd /boot/initramfs.cpio.gz
}
然后还是使用grub-mksecure
grub-mkrescue -o ../custom-linux.iso .
然后直接使用qemu启动
qemu-system-aarch64 \
-M virt \
-cpu cortex-a57 \
-m 1G \
-nographic \
-bios /usr/share/edk2/aarch64/QEMU_EFI.fd \
-cdrom custom-linux.iso
启动成功
[ 0.474067] Freeing unused kernel memory: 3264K
[ 0.474590] Run /bin/init as init process
starting pid 50, tty '': '/etc/init.d/rcS'
Mounting /proc, /sys, /dev...
=============================
Minimal BusyBox RootFS Booted
Running rcS...
Remounting rootfs rw...
=============================
Starting shell...
/bin/sh: can't access tty; job control turned off
~ # ls
bin dev etc lib proc root sbin sys usr
~ # uname -r
6.18.0
~ # dmesg | grep -i "initramfs"
[ 0.228230] Unpacking initramfs...
~ #