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根文件系统的步骤,我总结为四步走:
- 下载并配置Busybox:选择需要的命令,静态编译或动态编译
- 编译安装:生成
_install目录 - 补充目录和库:创建
/dev、/proc等目录,复制必要的动态库 - 制作文件系统镜像:打包成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目录下就有了bin、sbin、usr三个目录。但这还不够,我们需要手动补充一些东西。
我的经验:静态编译的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进程的启动细节。到时候再细聊。