4、根文件系统构建:Busybox制作、Yocto/OpenWrt构建、文件系统布局

说到根文件系统,很多刚入行的朋友容易把它和内核搞混。其实说白了,内核负责管理硬件、调度任务,而根文件系统提供的是用户空间的一切——命令、库、配置文件、应用程序。没有它,内核启动后就是个光杆司令,啥也干不了。

我这些年做嵌入式网关,根文件系统这块踩过的坑真不少。今天咱们就聊聊三种主流方案:Busybox、Yocto、OpenWrt。每种都有它的脾气,选对了事半功倍。

4.1 根文件系统到底长什么样?

先说说文件系统布局。Linux的根文件系统有一套约定俗成的结构,叫FHS(Filesystem Hierarchy Standard)。但嵌入式设备嘛,资源有限,我们通常会做精简。

一个典型的嵌入式根文件系统目录结构是这样的:

/
├── bin/        # 用户命令(ls、cp、cat等)
├── sbin/       # 系统管理命令(ifconfig、mount等)
├── usr/        # 用户程序和数据
│   ├── bin/
│   ├── sbin/
│   └── lib/
├── lib/        # 共享库和内核模块
├── etc/        # 配置文件
├── dev/        # 设备节点
├── proc/       # 进程信息虚拟文件系统
├── sys/        # 系统信息虚拟文件系统
├── tmp/        # 临时文件
├── var/        # 可变数据(日志、缓存)
├── mnt/        # 挂载点
└── root/       # root用户家目录

嗯,这里要注意:/proc/sys在启动时是空的,需要内核挂载procfs和sysfs后才能用。/dev可以用静态设备节点,也可以用devtmpfs或mdev动态管理。我个人习惯用devtmpfs,省心。

核心要点:根文件系统的大小直接影响启动速度和内存占用。对于网关设备,我建议把不必要的man手册、locale、文档统统砍掉。一个精简的根文件系统可以做到2MB以内。

4.2 Busybox——小而美的瑞士军刀

Busybox是什么?它把上百个Linux命令打包成一个二进制文件。你想想看,一个ls命令就要几十KB,如果每个命令都单独编译,那根文件系统得膨胀成什么样?Busybox通过符号链接,让一个程序扮演所有命令的角色。

制作Busybox根文件系统的步骤,我总结为四步走:

  1. 下载并配置Busybox:选择需要的命令,静态编译或动态编译
  2. 编译安装:生成_install目录
  3. 补充目录和库:创建/dev/proc等目录,复制必要的动态库
  4. 制作文件系统镜像:打包成squashfs、jffs2或ext4格式

具体操作如下:

# 下载Busybox
wget https://busybox.net/downloads/busybox-1.36.0.tar.bz2
tar -xjf busybox-1.36.0.tar.bz2
cd busybox-1.36.0

# 配置(使用默认配置,然后按需调整)
make defconfig
make menuconfig

# 静态编译(避免动态库依赖)
# 在menuconfig中设置:
# Settings -> Build static binary (no shared libs)

# 编译并安装
make -j4
make install

# 查看生成的目录
ls -l _install/

编译完成后,_install目录下就有了binsbinusr三个目录。但这还不够,我们需要手动补充一些东西。

我的经验:静态编译的Busybox虽然体积大一点(大约800KB),但好处是不依赖任何动态库,部署起来特别方便。我曾经在一个只有4MB Flash的网关设备上,用静态Busybox加上一个自定义应用,刚好塞进去。

接下来是补充库和目录:

cd _install

# 创建必要的目录
mkdir -p proc sys dev tmp etc/init.d mnt root var/log

# 复制动态库(如果是动态编译)
# 用交叉编译器的sysroot目录
cp -r /path/to/sysroot/lib/* ./lib/

# 创建设备节点(静态方式)
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3

# 创建初始化脚本
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
echo "Welcome to Embedded Gateway"
EOF
chmod +x etc/init.d/rcS

# 创建inittab
cat > etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::ctrlaltdel:/sbin/reboot
EOF

最后打包成文件系统镜像:

# 制作ext4镜像(适合SD卡或eMMC)
dd if=/dev/zero of=rootfs.ext4 bs=1M count=16
mkfs.ext4 rootfs.ext4
sudo mount -o loop rootfs.ext4 /mnt
sudo cp -r _install/* /mnt/
sudo umount /mnt

# 或者制作squashfs只读镜像(适合Flash)
mksquashfs _install/ rootfs.squashfs -comp xz

我曾经踩过的坑:有一次我忘了在etc/inittab里配置::sysinit,结果内核启动后直接panic,因为init进程找不到初始化脚本。还有一次,动态编译的Busybox忘了复制ld-linux.so,导致所有命令都报"file not found"。嗯,这些细节真不能马虎。

4.3 Yocto——企业级的构建系统

Busybox适合小项目,但如果你要构建一个包含几十个软件包、需要定制内核、还要管理依赖关系的网关系统,Yocto才是正解。

Yocto的核心概念是"层"(Layer)。每一层负责一类功能:meta层是基础,meta-oe层提供额外软件包,meta-qt5层提供Qt支持。你可以把Yocto想象成一个乐高积木系统,按需拼装。

一个典型的Yocto项目结构:

my-gateway/
├── layers/
│   ├── meta/              # 核心层(OE-Core)
│   ├── meta-poky/         # Poky参考发行版
│   ├── meta-openembedded/ # 额外软件包
│   └── meta-gateway/      # 自定义层(我们的网关应用)
├── build/
│   └── conf/
│       ├── local.conf     # 本地配置(目标架构、并行编译数)
│       └── bblayers.conf  # 层配置
└── poky/                  # Yocto源码

使用Yocto构建根文件系统,我一般这样操作:

# 初始化环境
source poky/oe-init-build-env build

# 编辑local.conf,设置目标硬件
# MACHINE = "qemuarm64"   # 或者你的具体硬件
# DISTRO = "poky"         # 发行版类型

# 添加自定义层
bitbake-layers create-layer ../layers/meta-gateway
bitbake-layers add-layer ../layers/meta-gateway

# 构建完整的系统镜像
bitbake core-image-minimal

# 生成的镜像在:
# build/tmp/deploy/images/qemuarm64/core-image-minimal-qemuarm64.rootfs.ext4

Yocto的强大之处在于它的依赖管理。比如你的应用需要libcurl,只需要在recipe里写上DEPENDS = "curl",Yocto会自动下载、编译、安装curl及其所有依赖。

个人建议:Yocto的学习曲线确实陡峭。我第一次用的时候,光配置环境就折腾了两天。但一旦上手,你会发现它的一致性非常好——所有软件包都用同样的方式编译、打包、部署。对于需要长期维护的网关产品,Yocto是值得投入的。

Yocto生成的根文件系统默认包含以下内容:

组件 说明 典型大小
Busybox 基础命令集 500KB-1MB
glibc C运行时库 2-5MB
udev 设备管理 200KB
dropbear SSH服务器 300KB
networking tools ifconfig、route等 100KB
自定义应用 网关业务逻辑 视情况而定

4.4 OpenWrt——为网络设备而生

如果你做的网关需要路由功能、WiFi支持、或者要用到大量的网络协议,OpenWrt可能是更好的选择。它本身就是为路由器/网关设计的,内置了完整的网络栈和包管理工具opkg。

OpenWrt的构建系统基于SDK,比Yocto轻量很多:

# 下载OpenWrt SDK
wget https://downloads.openwrt.org/releases/22.03.5/targets/armvirt/64/
    openwrt-sdk-22.03.5-armvirt-64_gcc-11.3.0_musl.Linux-x86_64.tar.xz
tar -xJf openwrt-sdk-*.tar.xz
cd openwrt-sdk-*

# 配置目标系统
make menuconfig
# 选择 Target System -> ARM
# 选择 Subtarget -> ARMv8

# 选择需要的软件包
# 在Base system中勾选:
# - busybox
# - dnsmasq
# - firewall
# - dropbear

# 构建
make -j4

# 生成的固件在:
# bin/targets/armvirt/64/openwrt-*-rootfs.tar.gz

OpenWrt的根文件系统布局和标准Linux略有不同。它的配置文件集中在/etc/config目录下,使用UCI(Unified Configuration Interface)统一管理。比如配置网络:

# /etc/config/network
config interface 'lan'
    option device 'eth0'
    option proto 'static'
    option ipaddr '192.168.1.1'
    option netmask '255.255.255.0'

config interface 'wan'
    option device 'eth1'
    option proto 'dhcp'

我的使用感受:OpenWrt的包管理真的很方便。在开发阶段,我经常用opkg install tcpdump临时装个抓包工具,调试完再卸载。这种灵活性是Yocto和Busybox给不了的。但代价是OpenWrt的根文件系统相对较大,一般需要8MB以上的Flash。

4.5 三种方案怎么选?

我根据实际项目经验,整理了一个对比表:

维度 Busybox Yocto OpenWrt
学习成本 低(1-2天) 高(2-4周) 中(1周)
根文件系统大小 1-5MB 8-50MB 4-16MB
包管理 有(RPM/DEP) 有(opkg)
定制灵活性 高(手动控制) 极高(层机制) 中(包选择)
适用场景 资源极度受限的设备 复杂产品、长期维护 网络功能为主的网关

我个人习惯这样选型:

  • 如果Flash小于8MB,RAM小于64MB → 用Busybox
  • 如果需要大量第三方软件包,或者团队多人协作 → 用Yocto
  • 如果主要功能是路由、VPN、防火墙 → 用OpenWrt

好了,根文件系统构建这块就聊到这儿。下一章咱们会深入文件系统的挂载流程和init进程的启动细节。到时候再细聊。