从零构建 BusyBox 最小根文件系统:init 进程、inittab 与 mdev 热插拔机制详解
一、内核启动后的真空地带:没有 rootfs 就没有用户空间
Linux 内核启动的最后一步是尝试挂载根文件系统并执行/sbin/init。如果 rootfs 缺失或 init 程序不存在,内核会陷入 kernel panic,输出标志性的错误信息:
No working init found. Try passing init= option to kernel. Kernel panic - not syncing: Attempted to kill init!在嵌入式开发中,尤其是为一块全新的 ARM 开发板做 BSP 移植时,第一步往往不是写驱动,而是构建一个能正确启动到 shell 提示符的最小根文件系统。BusyBox 被称为"嵌入式 Linux 的瑞士军刀"——它把init、sh、mount、ls、cp等上百个常用工具的实现在一个二进制文件中提供。本文完整记录从交叉编译 BusyBox 到构建可启动的 rootfs 镜像的全过程。
二、从 Kernel 到 Shell 的启动链路:init 进程与 inittab 的协同机制
Linux 启动到用户空间的第一个进程 PID=1 就是 init。BusyBox 的 init 实现了 SystemV 风格的精简版,其行为完全由/etc/inittab配置文件驱动。
sequenceDiagram participant Kernel as Linux Kernel participant Init as /sbin/init(PID=1) participant Dev as /dev 设备节点 participant RC as /etc/init.d/rcS participant Shell as /bin/sh(控制台) Kernel->>Init: 1. kernel_init() → run_init_process("/sbin/init") Note over Init: 2. 解析 /etc/inittab Init->>Init: 3. sysinit: 执行 /etc/init.d/rcS Init->>Dev: 4. mdev -s 扫描 /sys 创建设备节点 Init->>Dev: 5. echo /sbin/mdev > hotplug Init->>RC: 6. rcS 中挂载 proc, sysfs, tmpfs Init->>Shell: 7. respawn: /bin/sh (控制台登录) Shell->>Shell: 8. 用户交互等待 Note over Init,Shell: 若 shell 退出,respawn 自动重启inittab的格式是id:runlevel:action:process。实际的最小配置如下:
# /etc/inittab —— BusyBox init 配置文件 # 格式: <id>:<runlevel>:<action>:<process> # 系统初始化脚本(启动时执行一次,等待完成) ::sysinit:/etc/init.d/rcS # 控制台 shell(退出后自动重启 respawn) ::respawn:-/bin/sh # Ctrl+Alt+Del 重启 ::ctrlaltdel:/sbin/reboot # 关机时卸载文件系统 ::shutdown:/bin/umount -a -r三、生产级构建流程:交叉编译 BusyBox 与生成 ext4 镜像
以下步骤建立在一台 x86_64 Linux 宿主机上,目标板为 ARM Cortex-A(arm-linux-gnueabihf 工具链)。
Step 1 — 交叉编译 BusyBox:
# 下载 BusyBox(以 1.36.1 为例) wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 tar xjf busybox-1.36.1.tar.bz2 && cd busybox-1.36.1 # 使用默认配置并调整 make defconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- # 关键配置项(可通过 make menuconfig 图形化配置) # Settings → Build Options → Build static binary (no shared libs) → [*] # Settings → Installation Options → Don't use /usr → [*] # Coreutils → sync → [*] # Linux System Utilities → mount → [*] # Linux System Utilities → mdev → [*] (支持设备热插拔管理) # 静态编译——避免依赖目标板的动态库 make -j$(nproc) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make install CONFIG_PREFIX=../rootfs ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-Step 2 — 构建文件系统目录结构:
#!/bin/bash # build_rootfs.sh —— 构建最小 rootfs 目录结构 # 必须包含 FHS 3.0 标准要求的最小目录集 ROOTFS_DIR="./rootfs" # 创建必要目录(FHS 规范) mkdir -p ${ROOTFS_DIR}/{bin,sbin,etc,proc,sys,dev,tmp,lib,usr/{bin,sbin,lib},var/{run,log}} # 创建 rcS 初始化脚本 cat > ${ROOTFS_DIR}/etc/init.d/rcS << 'SCRIPT' #!/bin/sh # /etc/init.d/rcS —— 系统启动初始化脚本 # 由 inittab 中的 sysinit action 调用 echo "=== BusyBox RootFS Starting ===" # 1. 挂载虚拟文件系统(必须在创建设备节点之前) mount -t proc none /proc mount -t sysfs none /sys mount -t tmpfs none /tmp mount -t devpts none /dev/pts # 2. 挂载调试文件系统(可选,调试用) mount -t debugfs none /sys/kernel/debug # 3. 配置主机名 hostname "emb-linux" echo "emb-linux" > /proc/sys/kernel/hostname # 4. mdev 设备管理——冷插拔扫描 # -s: 系统启动时扫描 /sys/class 和 /sys/block # 为所有已有设备创建 /dev 下的设备节点 mdev -s # 5. 热插拔事件处理——内核通过 netlink 通知 mdev # 将 mdev 路径写入 hotplug 节点后, # 内核在设备增删时会自动调用 mdev echo /sbin/mdev > /proc/sys/kernel/hotplug echo "=== RootFS Initialization Complete ===" SCRIPT chmod +x ${ROOTFS_DIR}/etc/init.d/rcS # 创建 inittab cat > ${ROOTFS_DIR}/etc/inittab << 'CFG' ::sysinit:/etc/init.d/rcS ::respawn:-/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r CFG # 复制必要的 C 运行时库(如果不是静态编译则需要) # 静态编译可跳过此步骤 echo "[INFO] rootfs 目录结构已创建: ${ROOTFS_DIR}"Step 3 — 生成 ext4 镜像:
# 创建 64MB 的 ext4 镜像 # 实际大小根据 BusyBox 编译产物调整(静态编译约 1.5MB) dd if=/dev/zero of=rootfs.ext4 bs=1M count=64 mkfs.ext4 -L "rootfs" rootfs.ext4 # 挂载并复制文件 sudo mount rootfs.ext4 /mnt sudo cp -a ./rootfs/* /mnt/ sudo umount /mnt四、mdev 设备管理的边界:与 udev 的能力差距与选择依据
mdev 是 BusyBox 内置的迷你版 udev,差距主要在功能范围上:
| 特性 | mdev | udev/systemd-udevd |
|---|---|---|
| 设备节点创建 | 支持(基于 /sys) | 支持 |
| 热插拔事件处理 | 支持(通过 hotplug) | 支持(通过 netlink) |
| 自定义命名规则 | 有限(通过 /etc/mdev.conf) | 完整的规则引擎 |
| 固件加载 | 手动脚本 | 内置支持 |
| 二进制体积 | < 5KB | 约 1.5MB |
| 适用场景 | 嵌入式、单板系统 | 桌面、服务器 |
对于不使用动态内核模块的固定硬件平台(如定制 IoT 网关),mdev 完全足够。但如果系统需要 USB 设备的热插拔固件加载或基于设备属性的自定义节点命名,应考虑构建完整 udev。Init 进程的场景同样有选择——BusyBox init vs systemd。前者足够应对嵌入式场景,后者功能完备但镜像体积约 20 倍增长。
五、总结
从零构建 BusyBox 最小根文件系统的核心环节包括:静态交叉编译 BusyBox(避免库依赖)、按 FHS 规范创建目录树、配置 inittab 驱动 init 行为、利用 mdev 的冷热插拔机制管理设备节点。整个过程产出的 rootfs 镜像通常在 3MB 以内,可直接作为嵌入式 Linux BSP 的基础 rootfs。后续扩展通常沿着两个方向:按需追加 glibc 动态库以支持更复杂的应用程序,或将 ext4 替换为 squashfs 实现只读根文件系统以增强可靠性。