3. Rootfs核心概念
在嵌入式Linux系统中,内核启动的最后一步是挂载根文件系统(Rootfs),并执行第一个用户空间程序。Rootfs不仅是存放可执行文件、库、配置和设备节点的容器,更是整个系统运行秩序的“地基”。如果根目录结构混乱,或者关键目录缺失,系统将无法正常启动,甚至出现“Kernel panic - not syncing: No init found”这类经典错误。因此,理解文件系统层次标准FHS,掌握根目录下各关键目录的职责,是构建嵌入式Rootfs的第一课。
一、FHS:根文件系统的“交通法规”
FHS(Filesystem Hierarchy Standard,文件系统层次标准)由Linux基金会维护,它规定了类Unix系统中目录的命名、位置和用途。FHS的核心目标是让不同发行版、不同架构的Linux系统在目录结构上保持一致,使应用程序、脚本和运维人员能够以可预测的方式找到文件。对于嵌入式开发而言,遵循FHS并不意味着要照搬桌面发行版的全部目录,而是理解其分类逻辑:哪些目录是系统启动必需的,哪些是运行时动态生成的,哪些是可选的。
FHS将目录分为两类:一类是静态的、可共享的,如/usr、/bin、/lib;另一类是动态的、机器相关的,如/etc、/var、/run。嵌入式Rootfs通常只保留启动和运行所需的最小集合,但目录的语义必须与FHS保持一致,否则BusyBox、systemd、udev等工具会因找不到预期路径而报错。
二、根目录结构总览
一个典型的嵌入式Linux根目录如下所示。它看起来与桌面Linux相似,但每个目录的内容可能被大幅裁剪:
/
├── bin/ # 基础用户命令,如 sh、ls、cp、mount
├── sbin/ # 系统管理命令,如 init、ifconfig、reboot
├── etc/ # 系统配置文件,如 inittab、fstab、passwd
├── lib/ # 共享库和内核模块,如 libc.so、ld-linux.so
├── dev/ # 设备节点,如 console、ttyS0、mmcblk0
├── proc/ # procfs 挂载点,内核运行时信息
├── sys/ # sysfs 挂载点,设备与驱动模型
├── usr/ # 用户程序与只读数据,常含 /usr/bin、/usr/lib
├── var/ # 可变数据,如日志、锁文件、PID 文件
├── tmp/ # 临时文件,通常挂载为 tmpfs
├── root/ # root 用户的家目录
└── mnt/ # 临时挂载点
在嵌入式场景中,/bin、/sbin、/lib往往通过BusyBox和交叉编译工具链生成,/dev、/proc、/sys则由内核在启动时动态填充。理解“静态内容”与“动态内容”的分界,是设计Rootfs布局的关键。
三、关键目录的职责与实战要点
下面逐一分析六个最关键的目录。它们共同构成了系统启动和运行的最小闭环。
| 目录 | 核心职责 | 典型内容 | 嵌入式注意事项 |
|---|---|---|---|
/bin |
存放所有用户可用的基础命令 | sh、ls、cp、mv、cat、mount | 通常由BusyBox提供,通过软链接指向/bin/busybox |
/etc |
存放系统级配置文件 | inittab、fstab、passwd、group、profile | 必须包含inittab或init.d/rcS,否则init无法启动 |
/lib |
存放共享库和内核模块 | libc.so.6、ld-linux.so.3、modules/ | 交叉编译时需从工具链复制,注意软链接版本号 |
/dev |
存放设备节点,提供用户空间访问硬件的入口 | console、ttyS0、null、zero、mmcblk0 | 内核配置CONFIG_DEVTMPFS后自动生成,无需手动mknod |
/proc |
挂载procfs,暴露内核和进程运行时信息 | cpuinfo、meminfo、cmdline、mounts | 需在init脚本中执行mount -t proc proc /proc |
/sys |
挂载sysfs,展示设备、驱动、总线的层次模型 | class/net、devices、bus/usb、kernel | 需执行mount -t sysfs sysfs /sys,udev/mdev依赖它 |
四、案例:一个最小可启动Rootfs的目录布局
假设我们使用BusyBox构建一个ARM嵌入式系统的Rootfs。交叉编译完成后,执行make install,BusyBox会将可执行文件安装到_install目录。此时需要手动补齐关键目录和配置:
# 创建标准目录骨架
mkdir -p rootfs/{bin,sbin,etc,lib,dev,proc,sys,tmp,var,root,mnt}
mkdir -p rootfs/etc/init.d
mkdir -p rootfs/lib/modules
# 复制BusyBox
cp -a _install/* rootfs/
# 复制交叉编译工具链的C库
cp -a /opt/toolchain/arm-linux-gnueabi/lib/*.so* rootfs/lib/
# 创建最小inittab
cat > rootfs/etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
EOF
# 创建启动脚本rcS
cat > rootfs/etc/init.d/rcS << 'EOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t tmpfs tmpfs /tmp
/sbin/mdev -s
EOF
chmod +x rootfs/etc/init.d/rcS
这个案例展示了Rootfs构建的核心逻辑:目录结构先于文件内容,挂载脚本先于应用程序。如果/proc没有挂载,ps命令将无法工作;如果/dev/console不存在,内核启动日志将无处输出;如果/etc/inittab缺失,init进程会直接退出,导致内核恐慌。
/dev、/proc、/sys通常是空目录,由内核或init脚本在运行时动态填充。不要试图把设备节点静态地“烧录”进镜像,除非你有特殊需求(如早期启动阶段需要固定节点)。
五、常见误区与复盘
- 误区一:把桌面发行版的完整目录树照搬到嵌入式设备。嵌入式存储空间有限,
/usr/share、/var/lib等目录往往可以大幅裁剪,只保留运行时真正需要的文件。 - 误区二:忽略
/lib中动态链接器的路径。交叉编译的程序默认使用/lib/ld-linux.so.3,如果该软链接缺失或指向错误,所有动态链接程序都会报“No such file or directory”。 - 误区三:忘记挂载
/proc和/sys。许多应用程序(如top、ifconfig、udev)依赖这两个虚拟文件系统,缺失时表现为命令无输出或报错。 - 误区四:
/etc权限过松或过紧。/etc/passwd应可读,/etc/shadow应仅root可读。嵌入式系统若使用BusyBox的login,权限错误会导致无法登录。
/dev/console是否存在、/sbin/init是否可执行、/etc/inittab是否配置了sysinit动作。这三个条件缺一不可。
总结来说,FHS为Rootfs提供了目录语义的共识,而/bin、/etc、/lib、/dev、/proc、/sys这六个目录则构成了系统启动与运行的最小骨架。构建Rootfs时,先搭骨架、再填内容、最后验证挂载与权限,才能让内核顺利交棒给用户空间。