4、rootfs基础概念:文件系统层次结构(FHS)、根文件系统必备目录(/bin、/etc、/lib等)、init进程与系统初始化
在RK3568嵌入式Linux开发中,rootfs(根文件系统)是系统启动后挂载到根目录"/"的完整文件系统集合,它不仅是内核启动后用户态程序的载体,更是整个系统能够正常工作的基石。与PC上的Linux发行版不同,嵌入式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设计意图的基础上,否则可能导致软件包安装失败或运行时找不到文件。
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进程退出后重新启动它,保证串口终端始终可用。
/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的启动流程可以概括为以下步骤:
- 内核执行
/sbin/init,init获得PID=1 - init解析
/etc/inittab,建立动作表 - 执行所有
sysinit动作(通常指向rcS脚本) - 根据运行级别执行
wait动作(如启动网络服务) - 启动
respawn动作的进程(如getty终端) - 进入空闲循环,监控子进程状态
实战场景: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流程。
/etc/version文件记录构建时间和版本号,便于现场排障时快速定位rootfs版本。