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布局的关键。

嵌入式 Linux 根目录结构与关键目录职责 / 根目录 /bin 基础用户命令 sh / ls / cp / mount BusyBox 软链接 启动后即可用 /etc 系统配置 inittab / fstab passwd / group 静态文本,可裁剪 /lib 共享库与内核模块 libc.so / ld-linux.so modules/ 动态链接必需 /dev 设备节点 console / ttyS0 mmcblk0 / null devtmpfs 动态生成 /proc procfs 挂载点 cpuinfo / meminfo cmdline / mounts 内核运行时视图 /sys sysfs 挂载点 class / devices bus / kernel 设备驱动模型 启动依赖关系: 内核挂载 Rootfs → 执行 /sbin/init → init 读取 /etc/inittab → 启动 shell 与守护进程 /dev/console 必须存在,否则内核无法输出启动日志;/proc 与 /sys 由 init 脚本挂载,供用户空间读取内核状态 关键目录分类: 静态可执行与库(/bin、/sbin、/lib) 静态配置(/etc) 动态设备(/dev) 内核虚拟文件系统(/proc、/sys)

三、关键目录的职责与实战要点

下面逐一分析六个最关键的目录。它们共同构成了系统启动和运行的最小闭环。

目录 核心职责 典型内容 嵌入式注意事项
/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,权限错误会导致无法登录。
排障经验:当系统启动卡在“Freeing unused kernel memory”之后没有任何输出时,优先检查三个点:/dev/console是否存在、/sbin/init是否可执行、/etc/inittab是否配置了sysinit动作。这三个条件缺一不可。

总结来说,FHS为Rootfs提供了目录语义的共识,而/bin、/etc、/lib、/dev、/proc、/sys这六个目录则构成了系统启动与运行的最小骨架。构建Rootfs时,先搭骨架、再填内容、最后验证挂载与权限,才能让内核顺利交棒给用户空间。