从零构建自定义根文件系统 BusyBox + Linux Kernel
目标
从一份 Linux 内核 + 一个 BusyBox 二进制,构建出一个可启动的最小根文件系统(initramfs),启动后能拿到 shell。这个过程不依赖任何构建系统,全部手动完成,理解原理的最佳方式。
整体步骤
Linux 内核 → BusyBox 二进制 → 根文件系统目录结构 → cpio 打包 → initramfs
1. 准备工作目录
mkdir -p rootfs/{bin,sbin,etc,dev,proc,sys,tmp,var,usr/bin,usr/sbin,root}
这个目录结构就是最终根文件系统的文件布局。
2. 交叉编译 BusyBox
如果主机和目标架构相同(都是 x86_64),直接本机编译就行:
cd busybox-1.36.1
make defconfig
# 确保静态编译开启:
# make menuconfig -> Build Options -> Build BusyBox as a static binary = Y
make -j$(nproc)
# 检查产物
ls -la busybox
# 预期:~1.1MB(musl 静态编译)
如果是交叉编译,加上 CROSS_COMPILE 前缀:
make CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
# 验证架构
file busybox
# busybox: ELF 32-bit LSB executable, ARM, version 1
3. make install 到根文件系统
# 先确保 make install 的目标目录干净
rm -rf rootfs/*
# 安装 BusyBox
make CONFIG_PREFIX=../rootfs install
# 这会把 busybox 二进制和所有 Applet 符号链接放到 rootfs/ 下
# 验证
ls -la rootfs/bin/
# 应该看到 busybox + 一堆符号链接(ls -> busybox, cat -> busybox, ...)
4. 创建设备节点
在没有 devtmpfs 的情况下手动创建(内核配置里开启了 CONFIG_DEVTMPFS 的话可以跳过):
cd rootfs
mknod -m 600 dev/console c 5 1 # 控制台
mknod -m 666 dev/null c 1 3 # null
mknod -m 666 dev/zero c 1 5 # zero
mknod -m 666 dev/random c 1 8 # random
mknod -m 666 dev/urandom c 1 9 # urandom
mknod -m 666 dev/tty c 5 0 # tty
mknod -m 666 dev/tty0 c 4 0 # tty0(vga)
mknod -m 666 dev/ttyS0 c 4 64 # 串口 0
如果内核开了 devtmpfs,启动时会自动创建大部分设备节点,手动只需要 console 和 null。
5. 准备配置文件
# /etc/inittab — 最简单的配置
cat > etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
tty1::respawn:/sbin/getty 115200 tty1
ttyS0::respawn:/sbin/getty 115200 ttyS0
::ctrlaltdel:/sbin/reboot
EOF
# /etc/init.d/rcS — 启动脚本
mkdir -p etc/init.d
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
hostname "my-busybox-system"
echo "BusyBox system boot complete!"
EOF
chmod +x etc/init.d/rcS
# /etc/fstab(可选)
cat > etc/fstab << 'EOF'
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
EOF
6. 打包成 initramfs
cd rootfs
# 创建 cpio 归档(newc 格式是内核支持的)
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
cd ..
# 检查产物大小
ls -la initramfs.cpio.gz
# 预期:500KB-1.2MB(取决于 BusyBox 编译选项)
7. 准备内核
从 kernel.org 下载 Linux 内核源码,编译:
tar xjf linux-6.1.tar.xz
cd linux-6.1
# 用 defconfig 起步
make defconfig
# 确保开启 initramfs 支持:
# make menuconfig -> General setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support = Y
make -j$(nproc)
# 产物在 arch/x86/boot/bzImage
8. 打包成可启动镜像
最简单的方式是用 QEMU 测试:
qemu-system-x86_64 \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 root=/dev/ram0 init=/init" \
-nographic
如果一切顺利,你会看到内核启动日志,然后 BusyBox init 执行 rcS,最后进入登录 shell(ttyS0)。
故障排查
内核 panic:VFS Unable to mount root fs
- 内核没开 initramfs 支持,检查 CONFIG_BLK_DEV_INITRD
- initrd 参数没传对
- inittab 格式有误导致 init 崩溃
BusyBox init 没启动
- inittab 路径不对(应该是 /etc/inittab)
- init Applet 没编译进 BusyBox
- 内核启动参数里指定了 init=/busybox/init(或干脆 init=/busybox sh 跳过 inittab)
shell 启动后没反应
- getty 没编译进 BusyBox
- tty 设备节点没创建(devtmpfs 开了应该自动有)
- 试试直接用
init=/bin/sh启动,跳过 inittab