交叉编译与 QEMU 启动最小 Linux:一个“32 位内核跑 64 位程序“的启动陷阱(srvC)
交叉编译与 QEMU 启动最小 Linux:一个"32 位内核跑 64 位程序"的启动陷阱(srvC)
本篇基于 srvC(phase5)真实实操,是整个系列里最值得收藏的一篇。我们交叉编译 BusyBox 与内核、制作 initramfs 根文件系统、用 QEMU 把 ARM 和 x86 的最小 Linux 都跑了起来;但 x86 启动一路报
error -2→error -8,排查过程把"initramfs 启动失败"的几乎所有经典坑都踩了一遍。
1. 目标与链路
一条完整的"在 QEMU 里跑起 Linux"链路是:
BusyBox(根文件系统_app) + Linux内核 + 根文件系统(initramfs) + QEMU(机器模拟)- ARM 侧:交叉编译静态 ARM BusyBox → 编译
multi_v7_defconfig内核 →vexpress-a15板型 QEMU 启动 - x86 侧:用宿主机静态 x86_64 BusyBox → 编译
tinyconfig内核 →qemu-system-x86_64启动
2. ARM 链路:一次成功
ARM BusyBox 交叉编译(关掉会导致 glibc 2.39 不兼容的CONFIG_TC,静态链接):
sed-i's/^CONFIG_TC=y/# CONFIG_TC is not set/'.configmakeCROSS_COMPILE=arm-linux-gnueabihf--j8# 产出约 1.5M 的静态 ARM busybox,可在 qemu-arm-static 下直接运行内核与设备树:
makeARCH=arm multi_v7_defconfigmakeARCH=armCROSS_COMPILE=arm-linux-gnueabihf--j8zImage# 同时得到 arch/arm/boot/dts/arm/vexpress-v2p-ca15_a7.dtbQEMU 启动vexpress-a15:
qemu-system-arm-Mvexpress-a15-cpucortex-a15-m512M\-kernellinux-6.6.151/arch/arm/boot/zImage\-dtbvexpress-v2p-ca15_a7.dtb\-initrdrootfs-arm.cpio.gz-append'console=ttyAMA0 rdinit=/init'-nographic启动后/init跑演示脚本,打印进程列表并干净关机:
71 0 0:00 ps 演示命令执行完毕, 系统即将关机... [ 2.522735] reboot: Power downARM 侧multi_v7_defconfig默认开了BINFMT_ELF/BINFMT_SCRIPT,所以脚本化的/init直接就能跑。
3. x86 链路:第一道坎error -2
x86 侧用宿主机的静态 busybox 做根文件系统,qemu-system-x86_64一启动却卡在 init:
Run /init as init process Failed to execute /init (error -2) Run /sbin/init as init process Starting init: /sbin/init exists but couldn't execute it (error -40) Run /etc/init as init process Run /bin/init as init process Run /bin/sh as init process Kernel panic - not syncing: No working init found. Try passing init= option to kernel.error -2是ENOENT(文件/解释器找不到)。第一直觉是initramfs 里的软链接指错了路径。BusyBox 的busybox --install -s会在bin/、sbin/下生成大量指向 busybox 的软链接,如果目标写成绝对宿主机路径(如/root/lab/phase5/rootfs-x86/bin/busybox),在内存根文件系统里当然找不到。
抽取 cpio 检查真实目标,确认确实不对,修正为相对目标:
# bin/* 指向同目录兄弟 busybox;sbin/* 指向 ../bin/busyboxforlinbin/*;do[-L"$l"]&&ln-sfbusybox"$l";doneforlinsbin/*;do[-L"$l"]&&ln-sf../bin/busybox"$l";done修正后bin/sh -> busybox、sbin/init -> ../bin/busybox都在,重打 cpio 再启动——还是失败,只是报错从-2变成了-8。
4. 第二道坎error -8:内核没开二进制加载器
-8是ENOEXEC(执行格式错误)。抽内核配置一看,问题在tinyconfig默认把二进制加载器都关了:
$grep-E'BINFMT_ELF|BINFMT_SCRIPT'linux-src/.config CONFIG_BINFMT_ELF=# CONFIG_BINFMT_ELF is not setCONFIG_BINFMT_SCRIPT=# CONFIG_BINFMT_SCRIPT is not set内核根本不认识 ELF 和#!脚本,自然跑不了 busybox,也跑不了/init脚本。打开它们并重建:
./scripts/config--enableCONFIG_BINFMT_ELF ./scripts/config--enableCONFIG_BINFMT_SCRIPTmakeolddefconfig&&make-j$(nproc)bzImage再启动,报错仍-8。说明还有更深的根因。
5. 终极根因:32 位内核 vs 64 位 BusyBox
继续抽配置,决定性的一条出现了:
$grep-E'CONFIG_X86_32|CONFIG_64BIT'linux-src/.configCONFIG_X86_32=y# ← 内核被编成了 32 位!而根文件系统里的 busybox 是64 位 ELF(x86-64)。一个 32 位内核去exec一个 64 位程序,正是ENOEXEC——它压根不认这个格式。宿主机的chroot测试能跑(主机是 64 位内核),更反证了这一点:
$chroot/tmp/inspect_x86 /bin/sh-c'echo CHROOT_OK'CHROOT_OK修复:把内核翻回 64 位,重建 bzImage:
makeclean ./scripts/config--disableCONFIG_X86_32 ./scripts/config--enableCONFIG_64BIT# 同时保留 ELF/SCRIPT/INITRD/DEVTMPFS_MOUNTmakeolddefconfig&&make-j$(nproc)bzImage6. 终见曙光
qemu-system-x86_64-m512M-kernellinux-src/arch/x86/boot/bzImage\-initrdrootfs-x86.cpio.gz-append'console=ttyS0 rdinit=/init'-nographic--- 当前进程 --- PID USER COMMAND 1 0 {init} /bin/sh /init ← /init 脚本作为 1 号进程运行 25 0 {ps} /bin/sh /init 演示命令执行完毕, 系统即将关机... reboot: System haltedx86 的最小 Linux 终于干净启动并关机。
7. 排错决策树(收藏)
initramfs 启动失败,按错误码顺藤摸瓜:
| 错误码 | 含义 | 优先排查 |
|---|---|---|
error -2(ENOENT) | 文件/解释器找不到 | /init软链接目标、#!解释器在 initramfs 内是否存在 |
error -8(ENOEXEC) | 执行格式错误 | 内核是否开CONFIG_BINFMT_ELF/BINFMT_SCRIPT;内核位数与根文件系统里 ELF 的位数是否匹配(32-bit 内核 ≠ 64-bit 程序) |
一句话:先查软链接,再查BINFMT_*,最后一定核对内核架构和根文件系统里程序的架构是否一致。
8. 小结
- ARM 用
multi_v7_defconfig默认配置即开箱可用;x86 用tinyconfig需手动补BINFMT_ELF/SCRIPT并确认是 64 位内核; - initramfs 的软链接必须是相对路径、且指向 initramfs 内部存在的文件;
- “32 位内核跑 64 位 busybox” 这种架构错配,表现就是顽固的
ENOEXEC,靠chroot对比即可快速定位。
下一篇进 srvD:自己写内核模块、字符设备与 platform 总线驱动。
