4、嵌入式Linux根文件系统:BusyBox构建最小根文件系统、Yocto Project简介、文件系统布局、init进程与启动脚本
嵌入式Linux启动,内核加载完毕后,接下来就是根文件系统登场了。说白了,根文件系统就是系统启动后挂载的第一个文件系统,它决定了你的设备能跑什么、不能跑什么。我见过不少新手,内核配得漂漂亮亮,结果根文件系统没弄好,系统直接panic在挂载阶段——嗯,那种挫败感我懂。
4.1 根文件系统是什么?为什么它这么重要?
根文件系统(Root Filesystem)是Linux系统启动后挂载的第一个文件系统。它包含了系统启动所必需的文件和目录结构:init程序、库文件、配置文件、设备节点等。
你想想看,内核启动后,它需要找到一个init进程来接管用户空间。这个init进程在哪里?就在根文件系统里。如果根文件系统挂载失败,内核就只能干瞪眼,最后打印一句"Kernel panic - not syncing: No init found"然后死在那。
核心要点:根文件系统不是"一个文件",而是一个完整的目录树结构。它必须包含:
- /sbin/init 或 /bin/init —— 第一个用户空间进程
- /dev/console —— 控制台设备节点
- /lib —— 必要的动态链接库
- /etc/inittab —— init进程的配置文件
4.2 BusyBox:构建最小根文件系统的利器
说到最小根文件系统,BusyBox绝对是绕不开的工具。它把几百个常用的Linux命令打包成一个可执行文件,通过符号链接来区分不同的命令。我最早接触嵌入式Linux时,就是用BusyBox搭的第一个根文件系统,那会儿还踩了不少坑。
4.2.1 BusyBox的工作原理
BusyBox的核心思想很简单:一个二进制文件,多个入口点。当你执行ls时,实际上调用的是BusyBox,它根据argv[0]来判断要执行哪个命令。
# BusyBox的典型安装方式
# 1. 编译BusyBox
make menuconfig # 配置要包含的命令
make # 编译
make install # 安装到指定目录
# 2. 安装后你会看到这样的结构
/bin/ls -> /bin/busybox
/bin/cp -> /bin/busybox
/bin/mv -> /bin/busybox
/bin/cat -> /bin/busybox
# ... 所有命令都指向同一个busybox
我的经验:配置BusyBox时,别一股脑全选上。我建议只选你真正需要的命令,这样能显著减小根文件系统体积。曾经有个项目,Flash空间只有4MB,我硬是把BusyBox裁剪到只保留20个命令,系统照样跑得稳稳的。
4.2.2 构建最小根文件系统的步骤
我来给你捋一遍完整的构建流程。这个流程我在好几个项目里都用过,基本没出过问题。
- 编译BusyBox:配置、编译、安装到
_install目录 - 创建必要目录:
/dev、/proc、/sys、/etc、/lib等 - 添加设备节点:至少要有
/dev/console和/dev/null - 复制动态库:用
ldd检查BusyBox依赖的库,复制到/lib - 编写init配置文件:
/etc/inittab和/etc/init.d/rcS - 打包成镜像:cpio、squashfs、jffs2等格式
# 一个典型的最小根文件系统目录结构
/
├── bin
│ ├── busybox
│ ├── ls -> busybox
│ ├── sh -> busybox
│ └── ... (其他命令的符号链接)
├── dev
│ ├── console
│ └── null
├── etc
│ ├── inittab
│ └── init.d
│ └── rcS
├── lib
│ ├── ld-linux.so.3
│ └── libc.so.6
├── proc # 挂载点
├── sys # 挂载点
└── tmp # 临时文件
注意:设备节点/dev/console必须手动创建,不能用mdev或udev自动生成,因为它在系统启动初期就需要。我曾经犯过这个错,结果系统启动时控制台没有输出,排查了半天才发现是设备节点没创建。
4.3 Yocto Project简介
BusyBox适合快速搭建原型,但如果你要做产品级的嵌入式系统,Yocto Project会是更好的选择。Yocto是一个开源协作项目,提供了一套完整的嵌入式Linux构建框架。
说实话,Yocto的学习曲线确实有点陡。我第一次用Yocto时,光理解它的layer机制就花了两天。但一旦上手,你会发现它真的很强大。
4.3.1 Yocto的核心概念
| 概念 | 说明 | 我的理解 |
|---|---|---|
| Recipe(配方) | 描述如何构建一个软件包 | 相当于菜谱,告诉系统怎么"做菜" |
| Layer(层) | 一组配方的集合 | 就像搭积木,不同层提供不同功能 |
| Machine(机器) | 目标硬件平台的配置 | 告诉Yocto你的板子是什么 |
| Image(镜像) | 最终生成的根文件系统镜像 | 就是你要烧到板子上的东西 |
# Yocto的典型工作流程
# 1. 初始化构建环境
source oe-init-build-env
# 2. 配置local.conf
MACHINE = "qemuarm"
DISTRO = "poky"
# 3. 构建最小镜像
bitbake core-image-minimal
# 4. 输出文件在 tmp/deploy/images/qemuarm/
我的建议:如果你刚开始接触Yocto,别急着改太多配置。先用core-image-minimal跑通一遍流程,看看生成的根文件系统长什么样。等熟悉了再慢慢加自己的layer和recipe。
4.4 文件系统布局:FHS标准与嵌入式差异
Linux文件系统层次结构标准(FHS)定义了目录的用途。但在嵌入式系统中,我们往往需要做一些取舍。
标准FHS布局和嵌入式布局有什么区别?我举个例子:标准系统有/usr/share存放共享数据,但在嵌入式设备上,这个目录经常被省略,因为Flash空间太宝贵了。
| 目录 | 标准用途 | 嵌入式常用做法 |
|---|---|---|
| /bin | 用户命令 | BusyBox的符号链接 |
| /sbin | 系统管理命令 | 通常也是BusyBox的链接 |
| /usr | 用户程序和数据 | 经常省略,或只保留/bin和/lib |
| /var | 可变数据 | 挂载到tmpfs,重启即清空 |
| /tmp | 临时文件 | 挂载到tmpfs |
| /etc | 配置文件 | 必须保留,存放启动脚本和配置 |
嵌入式文件系统布局原则:
- 能省则省:不需要的目录和文件坚决不要
- 只读与可写分离:根文件系统通常只读,可写部分挂载到tmpfs或单独分区
- 符号链接代替复制:BusyBox就是最好的例子
4.5 init进程与启动脚本
init进程是用户空间的第一个进程,PID为1。它的职责是启动其他所有用户空间进程。在嵌入式系统中,最常用的init方案是BusyBox自带的init。
4.5.1 BusyBox init的工作流程
BusyBox init的启动流程其实很简单,我拆解给你看:
- 内核启动init进程(通常是
/sbin/init) - init读取
/etc/inittab配置文件 - 根据inittab执行系统初始化脚本
- 启动getty提供登录终端
- 进入等待状态,处理子进程退出信号
# 一个典型的/etc/inittab文件
::sysinit:/etc/init.d/rcS # 系统初始化脚本
::respawn:/sbin/getty -L ttyAMA0 115200 vt100 # 串口终端
::ctrlaltdel:/sbin/reboot # Ctrl+Alt+Del处理
::shutdown:/bin/umount -a -r # 关机时卸载文件系统
4.5.2 编写启动脚本rcS
/etc/init.d/rcS是系统初始化脚本,它负责挂载文件系统、配置网络、启动服务等。我习惯把rcS写得尽量简洁,每个功能一个函数,方便调试。
#!/bin/sh
# /etc/init.d/rcS - 系统初始化脚本
echo "Starting system initialization..."
# 挂载必要的文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
# 创建设备节点(如果使用mdev)
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
# 配置网络(静态IP示例)
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
# 启动其他服务
# ...
echo "System initialization complete."
我曾经踩过的坑:rcS脚本里如果有一个命令执行失败(比如网络配置出错),默认情况下脚本会继续执行。但如果你用了set -e,脚本会在第一个错误处退出。我建议在rcS里不要用set -e,而是手动检查关键步骤的返回值。
4.5.3 其他init方案
除了BusyBox init,嵌入式系统中还有几种常见的init方案:
- systemd:功能强大但体积大,适合资源丰富的设备
- SysV init:传统方案,通过运行级别管理服务
- OpenRC:Gentoo系的init系统,比SysV灵活
- 自定义init:有些项目直接写一个简单的C程序做init
我个人在资源受限的设备上,还是倾向于BusyBox init。它简单、可靠、体积小,而且我闭着眼睛都能配好。但如果你在做复杂的嵌入式系统,比如带图形界面、网络服务、数据库的,那systemd会是更好的选择。
4.6 本章小结
根文件系统是嵌入式Linux的基石。从BusyBox构建最小系统,到Yocto Project搭建完整平台,再到理解文件系统布局和init进程,每一步都关系到系统能否正常启动和运行。
嗯,我建议你动手实践一下:用BusyBox搭一个最小根文件系统,用QEMU跑起来看看。只有亲手做过,才能真正理解这些概念。下一章我们会讲存储设备驱动和文件系统选型,到时候你会用到今天学的根文件系统知识。