4、rootfs基础概念:文件系统层次结构(FHS)、根文件系统必备目录(/bin、/etc、/lib等)、init进程与系统初始化

在RK3568嵌入式Linux开发中,rootfs(根文件系统)是系统启动后挂载到根目录"/"的完整文件系统集合,它不仅是内核启动后用户态程序的载体,更是整个系统能够正常工作的基石。与PC上的Linux发行版不同,嵌入式rootfs需要根据硬件资源、应用场景和启动速度进行裁剪与定制。本章将围绕文件系统层次结构标准(FHS)、根文件系统必备目录、init进程与系统初始化三条主线展开,帮助读者建立从内核启动到用户态程序运行之间的完整认知链路。

RK3568 rootfs 核心概念关系图 Linux Kernel 启动 RK3568 SoC · 引导加载阶段完成 内核挂载 rootfs(根文件系统) initramfs / initrd / 直接挂载块设备 FHS 文件系统层次结构 /bin /sbin /etc /lib /usr /var /tmp /dev /proc /sys /mnt /opt 目录规范与用途定义 必备目录与关键文件 /bin/busybox → 基础命令 /etc/inittab → init配置 /lib/libc.so → 动态库 最小可运行集合 init 进程与系统初始化 PID=1 · 所有进程的祖先 读取 /etc/inittab 执行 rcS 启动脚本 BusyBox init / systemd 用户态应用程序与服务 shell · 业务进程 · 守护进程 · 网络服务 rootfs 是内核与用户态之间的桥梁:FHS 定义规范,必备目录保证可运行,init 完成从内核态到用户态的切换

4.1 文件系统层次结构标准(FHS)

FHS(Filesystem Hierarchy Standard)是Linux系统目录结构的规范标准,它定义了系统中各目录的用途、内容和组织方式。对于RK3568嵌入式开发而言,理解FHS不仅是为了"目录看起来规范",更直接关系到系统能否正确启动、软件包能否正常安装运行、以及后续维护是否高效。嵌入式系统虽然资源受限,但FHS的核心理念——将可变数据与不可变数据分离、将系统程序与用户程序分离、将运行时数据与静态数据分离——在裁剪rootfs时依然具有重要指导意义。

核心原理:FHS的目录分类逻辑

FHS将目录划分为两大类:静态目录(如/bin、/sbin、/lib、/usr)和动态目录(如/var、/tmp、/run)。静态目录在系统运行期间内容基本不变,适合放在只读分区;动态目录则频繁读写,需要放在可写分区。这种划分在嵌入式系统中尤为重要,因为RK3568开发板通常使用eMMC或SD卡作为存储介质,合理的分区规划可以延长Flash寿命并提升系统稳定性。

目录 类型 用途说明 RK3568嵌入式裁剪建议
/bin 静态 用户基本命令(ls、cp、sh等) 使用BusyBox提供,约1-2MB
/sbin 静态 系统管理命令(ifconfig、mount等) 与/bin合并或精简,约0.5MB
/etc 动态 配置文件、启动脚本、密码文件 必须保留,约1-5MB视配置而定
/lib 静态 共享库、内核模块 glibc约5MB,musl约1MB
/usr 静态 用户程序、库、文档 可合并到根目录,节省inode
/var 动态 日志、缓存、临时文件 挂载到tmpfs或单独分区
/tmp 动态 临时文件 挂载为tmpfs,重启清空
/dev 动态 设备节点 使用devtmpfs自动管理
/proc 虚拟 内核运行时信息 procfs,必须挂载
/sys 虚拟 设备模型与驱动接口 sysfs,必须挂载

在RK3568的实际开发中,我们通常不会严格遵循完整FHS,而是采用"精简FHS"策略。例如,将/bin和/sbin合并为单一目录,将/usr/lib链接到/lib,将/var/log挂载到tmpfs。这种裁剪必须建立在充分理解FHS设计意图的基础上,否则可能导致软件包安装失败或运行时找不到文件。

经验提醒:在裁剪rootfs时,建议先用完整FHS跑通系统,再逐步裁剪。每次裁剪后执行 ldd /bin/busybox 检查动态库依赖,使用 find / -type f | wc -l 统计文件数量,确保没有误删关键文件。

4.2 根文件系统必备目录与最小可运行集合

一个能够正常启动并进入shell的rootfs,必须包含一组"最小可运行集合"。这个集合由内核启动流程和用户态程序运行需求共同决定。对于RK3568平台,我们从内核挂载rootfs到init进程启动的完整链路来分析必备目录。

判断方法:从启动流程推导必备目录

当RK3568内核启动完成后,它会尝试挂载rootfs并执行第一个用户态程序(通常是/sbin/init)。这个过程中涉及的关键路径如下:

内核启动 → 挂载rootfs(块设备或initramfs)
→ 执行 /sbin/init(或 /init)
→ init读取 /etc/inittab
→ 执行 /etc/init.d/rcS
→ rcS挂载 /proc、/sys、/dev
→ 启动shell(/bin/sh)
→ 执行应用程序

从这个链路可以看出,rootfs至少需要包含以下内容:

  • /sbin/init:init程序本体,通常由BusyBox提供
  • /bin/sh:shell程序,用于执行脚本和交互
  • /etc/inittab:init的配置文件,定义运行级别和启动动作
  • /etc/init.d/rcS:系统初始化脚本
  • /lib:动态链接库(libc、ld-linux等)
  • /dev/console:控制台设备节点,内核输出依赖它
  • /proc、/sys挂载点目录

案例拆解:构建一个最小RK3568 rootfs

假设我们使用Buildroot构建RK3568的rootfs,其生成的最小目录结构如下(约8MB):

rootfs/
├── bin/          # busybox及其符号链接
│   ├── busybox
│   ├── sh → busybox
│   ├── ls → busybox
│   └── mount → busybox
├── sbin/
│   ├── init → busybox
│   └── ifconfig → busybox
├── etc/
│   ├── inittab
│   ├── init.d/
│   │   └── rcS
│   ├── passwd
│   ├── group
│   └── fstab
├── lib/
│   ├── libc.so.6
│   ├── ld-linux-aarch64.so.1
│   └── modules/    # 内核模块
├── dev/
│   ├── console
│   └── null
├── proc/          # 挂载点
├── sys/           # 挂载点
├── tmp/           # 挂载为tmpfs
├── var/
│   ├── log/
│   └── tmp/
└── usr/
    └── bin/       # 可选,存放额外工具

这个最小集合中,/etc/inittab的内容通常如下:

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

其中sysinit动作会在系统启动时执行一次rcS脚本,respawn则会在getty进程退出后重新启动它,保证串口终端始终可用。

风险提示:在RK3568上,如果/dev/console节点缺失或权限错误,内核启动后无法输出任何信息,且init进程会因无法打开控制台而失败。建议在构建rootfs时使用 mknod -m 600 dev/console c 5 1 创建该节点,并确保devtmpfs在rcS中被正确挂载。

4.3 init进程与系统初始化流程

init进程是Linux系统启动后第一个用户态进程,其PID固定为1。在RK3568嵌入式系统中,init进程承担着从内核态到用户态的"交接"职责,它决定了系统以何种方式启动服务、如何管理进程生命周期、以及如何响应系统关机/重启事件。

核心原理:BusyBox init的工作机制

BusyBox init是嵌入式系统中最常用的init实现,它通过解析/etc/inittab文件来控制系统初始化流程。与systemd相比,BusyBox init采用"顺序执行+简单守护"模型,没有并行启动、依赖管理、socket激活等高级特性,但胜在代码量小(约50KB)、依赖少、行为可预测,非常适合RK3568这类资源受限但要求高确定性的场景。

BusyBox init的启动流程可以概括为以下步骤:

  1. 内核执行/sbin/init,init获得PID=1
  2. init解析/etc/inittab,建立动作表
  3. 执行所有sysinit动作(通常指向rcS脚本)
  4. 根据运行级别执行wait动作(如启动网络服务)
  5. 启动respawn动作的进程(如getty终端)
  6. 进入空闲循环,监控子进程状态

实战场景:RK3568的rcS初始化脚本设计

在RK3568开发板的实际项目中,/etc/init.d/rcS脚本通常需要完成以下初始化任务:

#!/bin/sh
# RK3568 系统初始化脚本

# 1. 挂载虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev

# 2. 创建必要的设备节点(devtmpfs未覆盖时)
[ -e /dev/console ] || mknod -m 600 /dev/console c 5 1
[ -e /dev/null ] || mknod -m 666 /dev/null c 1 3

# 3. 设置主机名
hostname rk3568

# 4. 配置网络(静态IP示例)
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
route add default gw 192.168.1.1

# 5. 挂载可写分区
mount -t ext4 /dev/mmcblk0p4 /data

# 6. 启动应用服务
/usr/bin/application_server &
echo "RK3568 system init complete"

这个脚本展示了嵌入式系统初始化的典型模式:先挂载虚拟文件系统,再配置硬件基础环境,最后启动业务应用。值得注意的是,devtmpfs在Linux 3.x以后的内核中已经默认支持,内核会在挂载时自动创建设备节点,但为了兼容性和可靠性,rcS中仍然建议保留手动创建关键节点的逻辑。

案例拆解:init进程启动失败的排障过程

在某RK3568项目中,系统启动后内核日志显示Kernel panic - not syncing: No init found。通过分析启动日志和rootfs内容,我们定位到以下问题:

排查步骤 操作命令 发现的问题 解决方案
1. 检查rootfs挂载 内核参数 root=/dev/mmcblk0p2 分区存在但文件系统损坏 重新格式化ext4分区
2. 检查init路径 ls -l /sbin/init init符号链接指向busybox,但busybox缺失 重新拷贝busybox并验证权限
3. 检查动态库 ldd /bin/busybox libc.so.6找不到 将glibc库拷贝到/lib目录
4. 验证init执行 内核参数 init=/bin/sh shell可启动,确认rootfs基本可用 恢复init为/sbin/init

这个案例揭示了rootfs启动失败的三个典型原因:文件系统损坏、关键文件缺失、动态库依赖不完整。在实际开发中,建议在构建rootfs后立即进行"最小启动验证",即用init=/bin/sh参数启动系统,确认rootfs可挂载、shell可运行,再逐步恢复init流程。

总结复盘:rootfs的构建不是简单的文件拷贝,而是需要从内核启动需求、用户态运行需求、存储介质特性三个维度综合考虑。FHS提供了目录组织的规范框架,必备目录保证了系统可启动,init进程则完成了从内核到用户态的交接。在RK3568开发中,建议采用"最小化优先、逐步扩展"的策略:先构建一个能启动到shell的最小rootfs,验证通过后再添加业务组件,这样可以将问题隔离在可控范围内。
工程建议:在RK3568的rootfs开发中,务必使用版本控制管理rootfs的变更。推荐将rootfs构建脚本(Buildroot、Yocto或手动脚本)纳入Git管理,每次修改记录变更原因。同时,在rootfs中预留/etc/version文件记录构建时间和版本号,便于现场排障时快速定位rootfs版本。