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 构建最小根文件系统的步骤

我来给你捋一遍完整的构建流程。这个流程我在好几个项目里都用过,基本没出过问题。

  1. 编译BusyBox:配置、编译、安装到_install目录
  2. 创建必要目录/dev/proc/sys/etc/lib
  3. 添加设备节点:至少要有/dev/console/dev/null
  4. 复制动态库:用ldd检查BusyBox依赖的库,复制到/lib
  5. 编写init配置文件/etc/inittab/etc/init.d/rcS
  6. 打包成镜像: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的启动流程其实很简单,我拆解给你看:

  1. 内核启动init进程(通常是/sbin/init
  2. init读取/etc/inittab配置文件
  3. 根据inittab执行系统初始化脚本
  4. 启动getty提供登录终端
  5. 进入等待状态,处理子进程退出信号
# 一个典型的/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跑起来看看。只有亲手做过,才能真正理解这些概念。下一章我们会讲存储设备驱动和文件系统选型,到时候你会用到今天学的根文件系统知识。