3、Busybox构建:配置、编译与精简优化
Busybox 这东西,圈里人都叫它「瑞士军刀」。一个几兆的二进制文件,把 Linux 大几十个常用命令全包了。我刚开始做嵌入式那会儿,总觉得它就是个玩具,直到有一次项目 deadline 压得紧,才发现这玩意儿真能救命。
说白了,Busybox 就是把 ls、cp、mv、sh、init 这些工具揉成一个文件。你想想看,一个完整的 rootfs 可能几十兆,用 Busybox 能压到两三兆。对于 Flash 只有 8MB 的板子来说,这就是刚需。
3.1 配置阶段:别偷懒,手动裁剪
很多人图省事,直接 make defconfig 完事。嗯,这里要注意——默认配置会打开大量你用不上的功能。我见过一个项目,默认配置编译出来 1.8MB,手动裁剪后只有 480KB。差距就这么大。
核心原则:只选你需要的。板子上跑什么应用,就开什么命令。多一个 ls 的彩色输出,可能就多几十 KB 的代码段。
3.1.1 配置命令的选择
| 命令 | 适用场景 | 我的建议 |
|---|---|---|
make menuconfig |
交互式,逐项选择 | 新手首选,看得见摸得着 |
make defconfig |
快速生成默认配置 | 仅用于起步,后续必须裁剪 |
make allyesconfig |
全功能测试 | 千万别用在产品上,体积爆炸 |
make allnoconfig |
最小化起点 | 高手玩法,从零开始加功能 |
我个人习惯用 make menuconfig。虽然慢一点,但每个选项的 help 说明都能看到。有一次我为了省空间,把 FEATURE_TOP_SMP_CPU 关了,结果多核板子上 top 命令显示异常。后来老老实实看 help,才发现这个选项控制着多核 CPU 信息的采集。
3.1.2 必须关注的配置项
- Busybox Settings → Build Options:静态编译还是动态编译?我建议新手先用动态,调试方便。产品阶段再切静态,避免 libc 版本依赖问题。
- Busybox Settings → General Configuration:关掉
CONFIG_FEATURE_VERBOSE,能省不少空间。调试信息在生产环境没用。 - Linux System Utilities → mount:如果你不用 NFS 挂载,把
FEATURE_MOUNT_NFS关掉。这个选项会拉进 RPC 库,体积直接翻倍。 - Coreutils → cp, mv, rm:这些基础命令必须保留,但
FEATURE_CP_LONG_OPTIONS可以关。长参数 --preserve 之类的,嵌入式环境很少用。
小技巧:配置完成后,执行 make savedefconfig 生成精简的 defconfig 文件。下次编译直接 make defconfig 加载,省去重复配置的麻烦。
3.2 编译阶段:交叉编译与优化选项
交叉编译是嵌入式的家常便饭。你想想看,在 x86 的 PC 上编译出 ARM 的可执行文件,这中间的门道可不少。
3.2.1 设置交叉编译工具链
# 设置环境变量
export CROSS_COMPILE=arm-linux-gnueabihf-
export CC=${CROSS_COMPILE}gcc
export AR=${CROSS_COMPILE}ar
export STRIP=${CROSS_COMPILE}strip
# 配置并编译
make menuconfig
make -j$(nproc)
我曾经踩过一个坑:工具链版本不匹配。项目用的 glibc 2.28,但工具链是 2.23 的。编译出来的 Busybox 在板子上跑起来就段错误。折腾了两天才发现是 libc 版本冲突。所以,工具链版本必须和板子上的 libc 一致。
3.2.2 编译优化选项
| 选项 | 作用 | 体积影响 |
|---|---|---|
-Os |
优化代码体积 | 最小 |
-O2 |
优化性能 | 中等 |
-flto |
链接时优化,消除冗余 | 更小 |
-s |
strip 符号表 | 显著减小 |
我个人习惯用 -Os -flto -s 组合。在 Makefile 的 CFLAGS 里加上这些,编译出来的 Busybox 能再小 15%-20%。不过要注意,-flto 会大幅增加编译时间,第一次编译时别着急。
警告:不要在产品上使用 -O0 编译。虽然调试方便,但体积和性能都惨不忍睹。我见过有人把调试版固件发到产线,结果 8MB Flash 塞不下,整批返工。
3.3 安装阶段:构建最小 rootfs
编译完 Busybox 后,需要把它安装到 rootfs 目录中。这一步看似简单,但细节决定成败。
3.3.1 安装命令
# 安装到指定目录
make install CONFIG_PREFIX=/path/to/rootfs
# 查看安装结果
ls -l /path/to/rootfs/bin/busybox
# 输出:-rwxr-xr-x 1 root root 680K ... busybox
安装完成后,bin/busybox 就是那个「瑞士军刀」。其他命令都是指向它的软链接。比如 ls 其实是 busybox ls 的别名。
3.3.2 手动创建必要目录
mkdir -p rootfs/{bin,sbin,usr/bin,usr/sbin,etc,dev,proc,sys,tmp,var/log}
这些目录缺一不可。我遇到过有人忘了建 /dev,结果板子启动后控制台没输出,干瞪眼半小时才发现问题。
3.4 精简与优化技巧
这部分是实战干货。我做了这么多年嵌入式,总结了几条铁律。
3.4.1 删除无用命令
用 readelf 或 nm 分析 Busybox 的符号表,看看哪些函数被链接进来了。如果发现 telnetd、ftpd 这些服务端程序,而你根本不用,果断在 menuconfig 里关掉。
经验数据:一个典型的 IoT 设备,只需要 30-40 个命令。关掉不必要的命令,体积能减少 40%-60%。
3.4.2 使用 applet 子集
Busybox 支持按 applet 编译。在 menuconfig 里,每个命令都可以单独开关。我建议按以下分类处理:
- 必须保留:init, sh, ls, cp, mv, rm, cat, echo, mount, umount, reboot, poweroff
- 按需保留:vi, tar, gzip, find, grep, sed, awk(体积较大,慎用)
- 果断关闭:telnetd, ftpd, httpd, dhcpd(除非你确实需要服务器功能)
3.4.3 静态链接 vs 动态链接
| 方式 | 优点 | 缺点 |
|---|---|---|
| 静态链接 | 无外部依赖,体积可控 | 单个文件较大,更新麻烦 |
| 动态链接 | 共享 libc,总体积小 | 依赖 libc 版本,移植性差 |
我个人倾向静态链接。原因很简单:嵌入式设备的 libc 版本经常和开发环境不一致。静态链接虽然单个文件大一点,但省去了 libc 兼容性的烦恼。
3.4.4 终极优化:strip 与 sstrip
# 常规 strip
arm-linux-gnueabihf-strip busybox
# 更激进的 sstrip(需要单独安装)
sstrip busybox
sstrip 比 strip 更狠,它会删除 ELF 文件中的节头表。我试过,一个 680KB 的 Busybox,strip 后 520KB,sstrip 后 480KB。不过要注意,sstrip 后的文件不能用 objdump 分析,调试时不太方便。
避坑指南:我曾经在产线上用 sstrip 处理 Busybox,结果有一批芯片的 Flash 有坏块,sstrip 后的文件无法被 U-Boot 正确加载。从那以后,我只有在最终固件打包时才用 sstrip,开发阶段只用普通 strip。
3.5 本章知识体系
下面这张图总结了 Busybox 构建的核心流程。你可以把它当作操作清单。
这张图把整个流程串起来了。从配置到编译,再到安装和优化,每一步都有对应的子步骤。你照着这个流程走,基本不会出大问题。
嗯,关于 Busybox 构建,核心就这些。记住一句话:少即是多。你关掉的功能越多,出问题的概率就越小。嵌入式系统不是功能越多越好,而是越稳定越好。