3. Busybox 编译与配置:从源码到根文件系统的第一步

说实话,Busybox 是整个 rootfs 的基石。你想想看,一个只有 2MB 左右的二进制文件,却能模拟出上百个 Linux 标准命令——这就是嵌入式系统的魅力所在。我最早接触 Busybox 是在一个存储资源极度受限的 MIPS 路由器上,那时候我才真正理解什么叫「麻雀虽小,五脏俱全」。

这一节,我们就来亲手走一遍 Busybox 的下载、配置和编译流程。我会把那些文档里没写的坑,一个一个给你指出来。

3.1 下载 Busybox 源码

我个人习惯从官方站点获取源码,而不是用发行版仓库里的版本。为什么?因为官方源码最干净,没有杂七杂八的补丁干扰。

# 下载最新稳定版(以 1.36.1 为例)
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2

# 解压
tar -xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
小提示: 如果你在墙内网络,下载可能会慢。我一般提前把几个常用版本的 tarball 存到本地镜像服务器上。版本号建议选 LTS 或稳定版,不要追新——我在项目中吃过一次 1.35.0 的亏,有个 applet 的 behavior 变了,导致脚本全挂。

解压后,目录结构大致如下:

目录/文件 说明
applets/ Busybox 主入口和 applet 注册逻辑
libbb/ 公共库函数,比如字符串操作、文件 I/O
networking/ 网络相关命令(ping、ifconfig、telnetd 等)
util-linux/ Linux 工具类命令(mount、fdisk 等)
Config.in Kconfig 配置文件,menuconfig 的菜单定义
.config 编译配置结果文件(menuconfig 生成)

3.2 menuconfig 配置:把不需要的统统砍掉

配置 Busybox 的核心就是 make menuconfig。这个界面和 Linux 内核的配置界面同源,如果你配过内核,上手毫无压力。

make menuconfig

进去之后,你会看到一堆分类菜单。我一般按这个顺序来:

  1. Settings → Build static binary (no shared libs) —— 静态编译开关,后面细说
  2. Settings → Cross Compiler prefix —— 填你的交叉编译工具链前缀,比如 arm-linux-gnueabihf-
  3. Coreutils / Shells / Networking / ... —— 逐个勾选你需要的命令
核心原则: 只勾选 rootfs 真正需要的命令。一个 ls 命令大约占 4-6KB,一百个命令就是 500KB。在 flash 只有 8MB 的年代,这可不是小数目。

我举个例子。有一次我做工业控制器的 rootfs,设备只需要跑一个监控进程和网络通信。最后我选中的命令只有 28 个:

  • 必备: init、sh、mount、umount、cat、echo、ls、cp、mv、rm、mkdir、chmod、chown
  • 网络: ifconfig、ping、telnetd、udhcpc
  • 调试: ps、top、free、dmesg、kill、grep、sed、awk
  • 系统: reboot、poweroff、hwclock

其他的,比如 vi、ftpget、httpd,统统不选。最终 busybox 二进制只有 680KB,比全功能版本小了将近一半。

注意: 不要为了省空间把 init 和 sh 去掉。没有 init,内核 panic 后直接死机;没有 sh,你的启动脚本跑不起来。我曾经见过一个新手把 sh 取消掉,结果系统启动到一半就挂了,连错误信息都看不到。

3.3 静态编译 vs 动态编译:怎么选?

这是每个做 rootfs 的人都会纠结的问题。我直接给你结论:

对比项 静态编译 动态编译
二进制大小 约 1.5-2.5 MB 约 500-900 KB
依赖 无外部依赖 需要 libc.so、ld-linux.so 等
启动速度 稍快(省去动态链接时间) 稍慢(需要加载共享库)
升级维护 升级 busybox 需整体替换 可单独升级 libc 或 busybox
适用场景 极小系统、initramfs、单板 有完整文件系统的嵌入式 Linux

我的建议:

  • 如果你做的是 initramfs 或者 极小系统(flash < 8MB),用静态编译。省掉 libc 的 1-2MB 空间,很划算。
  • 如果你的 rootfs 已经有 glibc 或 musl,用动态编译。这样 busybox 本身更小,而且 libc 的 bug 修复可以独立进行。

静态编译的配置方法很简单:

make menuconfig
# 进入 Settings → Build static binary (no shared libs)
# 按 Y 选中
# 保存退出

动态编译则保持该项为 N,但你需要确保 rootfs 里有正确的 libc 和动态链接器。路径一般是 /lib/libc.so.6 和 /lib/ld-linux.so.3(ARM 平台)。

避坑指南: 我曾经在一个 ARM9 平台上用动态编译,结果忘了把 ld-linux.so 拷贝到 rootfs 里。启动后 busybox 直接报 "No such file or directory",连 ls 都用不了。排查了半天才发现是动态链接器缺失。所以,动态编译后一定要用 readelf -d busybox | grep NEEDED 检查依赖。

3.4 编译与验证

配置完成后,执行编译:

# 清理旧编译产物(第一次可跳过)
make clean

# 编译
make -j$(nproc)

# 安装到指定目录
make install CONFIG_PREFIX=./_install

编译完成后,_install 目录下会出现 bin/、sbin/、usr/ 等目录,里面是 busybox 的符号链接。你可以用 file 命令确认编译类型:

file _install/bin/busybox

# 静态编译输出示例:
# busybox: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked

# 动态编译输出示例:
# busybox: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked

我习惯再用 size 命令看一眼各段的大小:

arm-linux-gnueabihf-size _install/bin/busybox

# 输出示例:
#    text    data     bss     dec     hex filename
#  654321   12345    6789  673455   a45ef busybox

text 是代码段,data 是已初始化数据,bss 是未初始化数据。这三个值加起来,就是 busybox 在内存中的占用。你可以根据这个值评估你的 rootfs 是否放得下。

3.5 知识体系总览

下面这张图把 Busybox 编译配置的核心流程串起来了:

Busybox 编译配置核心流程 下载 Busybox 源码 make menuconfig 配置 选择静态编译 / 动态编译 静态编译(无外部依赖) 动态编译(需 libc + ld) make -j$(nproc) 编译 make -j$(nproc) 编译 file / size 验证结果 核心决策点:静态编译适合极小系统 / initramfs,动态编译适合有完整 libc 的 rootfs

3.6 几个实用技巧

  • 保存配置: 配好 menuconfig 后,把 .config 文件备份到版本库。下次直接 cp .config.bak .config && make oldconfig,省得重新点菜单。
  • 查看具体命令大小: 用 make V=1 可以看详细编译日志,但更实用的方法是编译后用 du -h _install/bin/busybox 看整体大小。
  • 交叉编译: 别忘了在 menuconfig 里设置 Cross Compiler prefix,或者在命令行传参:make CROSS_COMPILE=arm-linux-gnueabihf-。
常见错误: 如果你在 x86 机器上编译 ARM 的 busybox,却忘了设交叉编译前缀,编译出来的二进制在目标板上跑不起来。错误信息通常是 "Exec format error"。用 file 命令一看,发现是 x86 的 ELF,那就尴尬了。

好了,Busybox 的编译配置就讲到这里。你拿到一个能用的 busybox 二进制之后,下一步就是把它放到 rootfs 里,配上 init 脚本和必要的设备节点。这些内容我们后面会详细展开。