2. 嵌入式Linux根文件系统基础

在嵌入式 Linux 系统中,内核负责管理 CPU、内存、外设和驱动,而用户真正能“看见并使用”的系统环境,几乎都由根文件系统提供。内核启动完成后,会尝试挂载一个根文件系统,并执行其中的第一个用户态程序。如果根文件系统缺失、损坏或结构不符合预期,系统即使内核正常,也会停在启动阶段,无法进入可交互环境。因此,理解根文件系统的概念、目录标准、核心组件和启动流程,是发布 SDK 根文件系统版本工程的基础。

一、根文件系统到底是什么

根文件系统(Root Filesystem,简称 rootfs)是内核挂载在“/”目录下的文件系统。它不只是存放文件的目录集合,还承担着用户态运行环境、系统初始化、设备节点管理、库依赖、配置文件和应用程序入口等职责。嵌入式设备常见的根文件系统形态包括:

  • 只读压缩镜像:如 squashfs、cramfs,体积小、启动快,适合量产固件。
  • 可读写文件系统:如 ext4、ubifs、jffs2,适合需要保存配置或日志的设备。
  • 内存文件系统:如 initramfs、tmpfs,常用于早期启动或临时根环境。
  • 混合方案:只读根文件系统叠加可写分区,兼顾稳定性和可配置性。

从发布工程角度看,根文件系统不是“随便打包一个目录”,而是需要明确版本、依赖、权限、启动脚本和升级策略的软件制品。

二、目录结构标准:FHS 与嵌入式裁剪

Linux 根文件系统通常遵循 FHS(Filesystem Hierarchy Standard)思想,但嵌入式系统会根据资源限制进行裁剪。常见目录及其职责如下:

目录 主要职责 嵌入式常见内容
/bin 基础用户命令 busybox 软链接、sh、ls、cp
/sbin 系统管理命令 init、ifconfig、reboot
/etc 系统配置 inittab、fstab、网络配置
/lib 共享库与内核模块 libc.so、ld-linux.so、modules
/dev 设备节点 console、tty、mtdblock
/proc、/sys 内核与设备信息接口 启动时挂载的虚拟文件系统
/usr 用户程序与资源 应用、脚本、资源文件
/var 可变数据 日志、锁文件、运行时状态

在嵌入式场景中,/usr 常被合并或简化,/var 可能挂载为 tmpfs,/dev 由 devtmpfs 或 mdev 动态管理。发布根文件系统时,必须确认这些目录的权限、属主和挂载点是否符合目标设备要求。

三、BusyBox 与系统库:用户态的最小骨架

BusyBox 是嵌入式 Linux 中最常见的用户态工具集。它把 ls、cp、mv、sh、ifconfig、mount 等大量命令集成到一个可执行文件中,通过软链接或 applet 机制提供不同命令入口。这样做可以显著减小根文件系统体积,同时保持基本 Shell 环境可用。

# 典型 BusyBox 安装后的目录结构
/bin/busybox
/bin/ls -> busybox
/bin/cp -> busybox
/sbin/init -> ../bin/busybox

系统库则是用户态程序运行的基础。动态链接的程序依赖 libc、libm、libpthread、libdl 等共享库,以及动态加载器 ld-linux.so。发布时必须确保:

  • 库文件与目标架构匹配,如 arm、aarch64、mips。
  • 库版本与应用程序、BusyBox 的编译环境一致。
  • 动态加载器路径正确,避免出现 “No such file or directory” 但文件实际存在的情况。
  • 必要时使用 strip 减小体积,但保留调试符号用于问题定位。
判断一个可执行文件依赖哪些库,可以使用 readelf -d 或 arm-linux-gnueabihf-objdump -p 查看 NEEDED 项,再逐一确认根文件系统中是否存在对应库。

四、启动流程:从内核到用户态

嵌入式 Linux 的启动流程可以概括为:BootROM → Bootloader → Kernel → 根文件系统 → init → 用户应用。根文件系统相关部分主要集中在后三个阶段。

嵌入式 Linux 根文件系统启动链路 BootROM 上电执行固化代码 Bootloader 加载内核与设备树 Kernel 初始化驱动与 VFS 挂载 rootfs squashfs / ext4 / ubifs 执行 init /sbin/init 或 /init 根文件系统内部关键对象 /etc/inittab 初始化配置 /etc/init.d/rcS 启动脚本 /lib 与 /usr/lib 共享库依赖 /dev 与 /proc 设备与内核接口 任一环节缺失都可能导致内核恐慌、init 失败或应用无法启动

内核挂载根文件系统后,会寻找并执行 init 程序。嵌入式系统常见两种路径:一是 /sbin/init,通常由 BusyBox 提供;二是 /init,常见于 initramfs 或定制启动方案。init 会读取 /etc/inittab,执行 /etc/init.d/rcS 等脚本,完成挂载虚拟文件系统、设置网络、启动应用等操作。

# 典型 /etc/inittab 片段
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot

五、案例与常见误区

假设某 ARM 设备内核启动日志停在 Kernel panic - not syncing: No init found。这通常不是内核问题,而是根文件系统没有正确挂载,或 init 程序不存在、权限错误、动态库缺失。排查顺序可以是:

  1. 确认内核命令行中的 root= 与 rootfstype= 是否匹配实际存储分区。
  2. 确认根文件系统镜像已正确烧录,文件系统类型被内核支持。
  3. 确认 /sbin/init 或 /init 存在且具有可执行权限。
  4. 使用 readelf -d 检查 init 依赖的共享库是否齐全。
  5. 确认 /dev/console 存在,否则 init 可能无法打开控制台。
常见误区是只关注“文件有没有”,而忽略“权限、链接、架构、库依赖和挂载点”。根文件系统发布必须把目录结构、BusyBox、系统库和启动脚本作为一个整体验证,而不是单独替换某个文件。

从版本工程角度看,根文件系统应当像内核和 Bootloader 一样被版本化管理:记录 BusyBox 版本、libc 版本、目录清单、启动脚本变更和镜像校验值。只有这样,后续发布、回滚和问题定位才有可靠依据。