3. Buildroot核心概念

根文件系统(Root Filesystem,简称 rootfs)是嵌入式Linux系统启动后挂载的第一个文件系统,它包含了系统运行所必需的全部应用程序、库文件、配置文件和设备节点。对于基于RK3568的嵌入式产品开发而言,根文件系统的定制直接决定了系统的功能边界、启动速度、存储占用和安全性。本章将深入剖析根文件系统的目录结构、关键目录的职责,并梳理从内核启动到用户空间初始化的完整流程,帮助读者建立对系统启动链路的整体认知。

RK3568 Linux 启动流程与根文件系统结构 BootROM 固化于RK3568芯片内部 加载SPL到SRAM SPL (U-Boot SPL) 初始化DDR控制器 加载U-Boot到DDR U-Boot 初始化外设/显示 加载kernel到DDR Kernel 解压/初始化子系统 挂载rootfs 根文件系统 (rootfs) 关键目录结构 /bin 用户命令 (ls, cat, sh) /sbin 系统管理命令 (init, mount) /etc 配置文件 (inittab, fstab) /lib 共享库/内核模块 (libc.so, modules) /dev 设备节点 (ttyS0, mmcblk0) /proc /sys 虚拟文件系统 内核运行时数据 /usr 用户程序/库 /var 可变数据/日志 /tmp 临时文件 /mnt 挂载点 /opt 可选软件 /home /root 用户目录 init 进程 (PID 1) 读取 /etc/inittab → 执行系统初始化脚本 → 启动getty终端 → 加载服务程序 → 成为所有用户进程的祖先 内核挂载rootfs后 Buildroot 定制根文件系统 = 目录结构 + 库/工具链 + 配置 + init 机制

3.1 根文件系统目录结构:FHS标准与嵌入式裁剪

Linux根文件系统的目录结构遵循Filesystem Hierarchy Standard(FHS)标准,该标准定义了系统中各目录的用途和存放内容。对于嵌入式设备,由于存储空间有限(RK3568开发板常见的eMMC为8GB~64GB,NAND Flash更小),我们通常不会完整照搬桌面Linux的全部目录,而是根据实际需求进行裁剪。但核心目录结构必须保留,否则系统将无法正常运行。

在RK3568的Buildroot构建产物中,output/target/目录即为生成的根文件系统骨架。开发者可以通过make menuconfig选择需要的软件包,Buildroot会自动将对应的二进制文件、库和配置文件放置到正确的位置。下表列出了嵌入式Linux根文件系统的核心目录及其功能说明:

目录存放内容RK3568场景说明裁剪建议
/bin所有用户可用的基本命令busybox提供的ls、cat、cp、sh等必须保留,可精简为busybox单二进制
/sbin系统管理命令(仅root可用)init、mount、ifconfig、reboot等必须保留,与系统启动和网络配置相关
/etc系统配置文件inittab、fstab、passwd、profile等必须保留,按需精简配置项
/lib共享库和内核模块libc.so、ld-linux、内核modules目录必须保留,可用musl libc减小体积
/dev设备节点文件ttyS0、mmcblk0、gpiochip等使用devtmpfs或mdev动态管理
/proc进程和内核信息虚拟文件系统CPU信息、内存状态、中断等必须挂载,由内核动态生成
/sys设备模型和驱动信息GPIO、I2C、SPI设备节点必须挂载,用于设备管理和驱动调试
/tmp临时文件运行时临时数据可挂载为tmpfs,掉电丢失
/var可变数据(日志、锁文件)系统日志、dhcp租约文件可挂载为tmpfs或精简
/usr用户程序和数据(二级目录)应用程序、库文件、文档嵌入式可合并到根目录,减少层级
/mnt临时挂载点U盘、SD卡挂载保留空目录即可

核心原理:FHS标准的意义在于为系统提供一致的路径约定。当应用程序按照标准路径查找配置和库文件时,系统才具有可移植性。在嵌入式环境中,我们可以在保持FHS精神的前提下进行裁剪——例如将/usr/bin合并到/bin,将/usr/lib合并到/lib,但绝不能随意更改/etc、/dev、/proc等关键目录的语义。

3.2 关键目录深度解析:/bin、/etc、/lib的职责与协作

在根文件系统的众多目录中,/bin、/etc、/lib三个目录构成了系统运行的最小闭环。/bin提供可执行程序,/lib提供程序运行所需的动态库,/etc提供程序运行所需的配置参数。三者缺一不可,且存在严格的依赖关系。

/bin目录:在Buildroot生成的系统中,/bin通常是一个指向/usr/bin的符号链接,或者直接包含busybox的符号链接集合。Busybox是一个集成了数百个常用命令的精简可执行文件,通过符号链接方式实现多命令复用。例如/bin/ls实际上是指向/bin/busybox的符号链接。这种设计极大节省了存储空间——一个完整的busybox二进制文件通常只有500KB~1MB,而对应的独立工具集合可能需要数十MB。

/etc目录:这是系统配置的核心。在RK3568系统中,/etc/inittab定义了init进程的启动行为,/etc/fstab定义了文件系统的挂载表,/etc/init.d/rcS是系统初始化脚本。Buildroot生成的/etc目录通常包含以下关键文件:

/etc/
├── inittab        # init进程配置文件
├── fstab          # 静态文件系统挂载表
├── init.d/
│   └── rcS        # 系统启动脚本
├── profile        # 用户登录环境变量
├── passwd         # 用户账户信息
├── group          # 用户组信息
├── shadow         # 加密密码(可选)
├── hostname       # 主机名
├── resolv.conf    # DNS配置
└── mdev.conf      # 设备节点管理规则(使用mdev时)

/lib目录:动态链接库是系统运行的基础。使用ldd命令可以查看一个可执行文件依赖哪些共享库。在RK3568的ARM64架构下,主要的库包括:

  • libc.so.6:C标准库,所有C程序的基础依赖
  • ld-linux-aarch64.so.1:动态链接器/加载器
  • libm.so.6:数学库
  • libpthread.so.0:POSIX线程库(新版glibc已合并到libc)
  • libgcc_s.so.1:GCC运行时支持库
  • modules/:内核模块目录,存放可加载的驱动模块(.ko文件)

实战经验:在裁剪/lib目录时,切忌盲目删除看似"没用"的库文件。建议使用arm-rockchip830-linux-uclibcgnueabihf-ldd(或对应的交叉编译工具链ldd)逐一检查/bin和/sbin下所有可执行文件的动态库依赖,确保每个库都被需要。同时,内核模块的modules.dep文件记录了模块间的依赖关系,删除模块时需同步更新该文件。

3.3 启动流程详解:从内核到init进程的完整链路

理解启动流程是定制根文件系统的前提。RK3568的启动流程可以分为四个阶段:BootROM引导、SPL/U-Boot引导、内核启动、init进程初始化。其中,根文件系统在内核启动阶段被挂载,而init进程则是内核启动后执行的第一个用户空间程序。

内核挂载根文件系统的机制:内核通过root=启动参数指定根文件系统所在设备。在RK3568平台上,常见的启动参数如下:

root=/dev/mmcblk0p5 rootwait rw
# /dev/mmcblk0p5: eMMC第5个分区
# rootwait: 等待设备就绪(适用于SD卡/eMMC等慢速设备)
# rw: 以读写方式挂载根文件系统

内核挂载根文件系统的过程遵循以下步骤:

  1. 解析root=参数,确定根设备
  2. 调用对应文件系统驱动(ext4、ubifs、squashfs等)
  3. 如果根设备是块设备且需要等待,则等待设备就绪
  4. 挂载根文件系统到/目录
  5. 执行init=参数指定的程序(默认为/sbin/init)

init进程的使命:init进程(PID 1)是所有用户进程的祖先。如果init进程崩溃或退出,内核将触发Kernel Panic。在Buildroot默认配置中,init进程由busybox提供,其行为由/etc/inittab控制。典型的inittab文件内容如下:

::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttyS0 115200 vt100
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

各字段含义为:id:runlevels:action:process。其中action字段定义了进程的执行方式:

  • sysinit:系统初始化时执行一次
  • respawn:进程终止后自动重启(用于getty终端)
  • askfirst:首次启动时询问用户(常用于串口控制台)
  • wait:等待进程执行完毕再继续
  • once:只执行一次,不等待
  • ctrlaltdel:捕获Ctrl+Alt+Del组合键
  • shutdown:系统关机时执行

常见误区:很多开发者误以为/etc/init.d/rcS是必须的。实际上,rcS只是约定俗成的系统初始化脚本,其内容完全由开发者自定义。在Buildroot中,你可以通过BR2_ROOTFS_OVERLAY选项将自己的rcS脚本覆盖到目标系统中。但无论脚本内容如何,sysinit动作必须存在,否则系统将无法完成初始化。

3.4 案例拆解:基于RK3568的根文件系统启动排障

下面通过一个实际案例来展示根文件系统启动问题的排查过程。假设我们使用Buildroot为RK3568构建了一个最小系统,烧录到eMMC后上电,串口输出如下日志:

VFS: Cannot open root device "mmcblk0p5" or unknown-block(179,5): error -6
Please append a correct "root=" boot option; here are the available partitions:
 179:0 7634944 mmcblk0 
 179:1 131072 mmcblk0p1 
 179:2 131072 mmcblk0p2 
 179:3 262144 mmcblk0p3 
 179:4 262144 mmcblk0p4 
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,5)

现象分析:内核报告无法打开mmcblk0p5设备,错误码-6表示ENXIO(设备不存在)。从分区列表可以看到,eMMC只有4个分区(p1~p4),没有p5分区。这说明U-Boot传递的root=参数与实际的eMMC分区表不匹配。

排查步骤:

  1. 检查U-Boot环境变量:在U-Boot命令行执行printenv bootargs,确认root=参数值
  2. 检查eMMC分区表:在U-Boot中执行mmc part查看实际分区布局
  3. 对比Buildroot的genimage.cfg配置文件,确认分区编号是否正确

根因定位:经过检查发现,Buildroot的genimage配置中定义了5个分区,但实际烧录时使用了旧的烧录脚本,只烧录了前4个分区。第5个分区(根文件系统分区)未被创建。

解决方案:重新执行完整的烧录流程,确保所有分区都被正确写入。同时,在U-Boot的bootargs中增加rootwait参数,避免因eMMC设备初始化延迟导致内核过早尝试挂载根文件系统。

复盘总结:此类问题在嵌入式开发中非常常见,根因往往是分区表与启动参数不一致。建议在开发初期就将分区布局固化为文档,并在烧录脚本中增加分区校验逻辑。此外,使用root=PARTUUID=方式替代设备节点名,可以避免因设备节点编号变化导致的挂载失败。

3.5 根文件系统的挂载选项与优化策略

根文件系统的挂载方式直接影响系统的启动速度和运行稳定性。在RK3568平台上,常见的根文件系统类型包括ext4(eMMC/SD卡)、UBIFS(NAND Flash)、SquashFS(只读压缩文件系统)和initramfs(内存文件系统)。

挂载选项对比:

文件系统适用存储读写特性压缩启动速度典型场景
ext4eMMC/SD卡读写不支持中等通用Linux系统
UBIFSNAND Flash读写支持(LZO/ZLIB)较慢NAND Flash设备
SquashFS任何块设备只读支持(GZIP/LZMA)快只读系统、恢复分区
initramfs内存读写支持(CPIO格式)最快早期用户空间、系统恢复

优化建议:对于RK3568的eMMC启动场景,推荐使用ext4文件系统并开启noatime挂载选项,减少不必要的元数据写入,延长eMMC寿命。对于NAND Flash方案,UBIFS是更合适的选择,但需要在内核中启用对应的驱动支持。如果系统需要快速启动且不需要持久化存储,可以考虑initramfs方案,将整个根文件系统加载到内存中运行。

关键决策点:选择根文件系统类型时,需要综合考虑存储介质特性、读写需求、启动速度要求和可靠性要求。例如,如果产品需要OTA升级,则根文件系统必须支持读写;如果系统只运行固定功能且不需要更新,SquashFS配合overlayfs可以实现"只读底層+可写上层"的混合方案,兼顾安全性和灵活性。

3.6 常见误区与总结复盘

在根文件系统定制过程中,开发者常犯以下错误,需要特别注意避免:

  • 误区一:盲目追求最小体积。过度裁剪可能导致系统功能缺失或运行时错误。建议先构建完整系统,验证功能后再逐步裁剪,每次裁剪后都要进行完整的启动测试。
  • 误区二:忽略设备节点的创建。在devtmpfs不可用的情况下,需要手动创建/dev下的设备节点。使用mdev(busybox提供)可以自动管理设备节点,但需要正确的/etc/mdev.conf配置。
  • 误区三:动态库依赖不完整。使用ldd检查依赖时,需要确保使用的是目标系统的交叉编译工具链,而不是宿主机的ldd,否则会得到错误的依赖列表。
  • 误区四:init进程配置错误。inittab中的语法错误或路径错误会导致系统启动失败。建议在修改inittab后,先在宿主机上用busybox的init -q进行语法检查。
  • 误区五:忽略文件权限和所有权。某些程序需要特定的文件权限才能运行。例如,/etc/shadow必须为root所有且权限为600,否则用户登录功能将失效。

总结:根文件系统是嵌入式Linux系统的"骨架",其目录结构、关键目录配置和启动流程构成了系统运行的基础。在RK3568平台上,通过Buildroot可以高效地定制根文件系统,但开发者必须深入理解每个目录的职责、init进程的工作机制以及内核挂载根文件系统的过程。只有掌握了这些底层原理,才能在遇到启动故障时快速定位问题,在功能裁剪时做出正确决策。下一阶段,我们将进入Buildroot的实战配置,学习如何从零开始构建一个适用于RK3568的定制化根文件系统。