3、BusyBox裁剪:配置原理、menuconfig操作与编译策略
BusyBox 这东西,圈内人都叫它「瑞士军刀」。一个几 MB 的二进制文件,能顶几十个 Linux 标准命令。但问题也来了——默认编译出来的 BusyBox 啥都带,很多 applet 你根本用不上。我见过有人把整个 rootfs 塞进 32MB Flash,结果 BusyBox 就占了 8MB,里面一半都是没用的命令。
说白了,裁剪 BusyBox 就是做减法。把不需要的 applet 拿掉,把用不上的功能关掉,最后得到一个又小又快的工具集。这一节,我就带你走一遍完整的裁剪流程。
3.1 BusyBox 配置原理
BusyBox 的配置系统跟 Linux 内核一模一样。它基于 Kconfig 和 Makefile,通过 .config 文件控制每个 applet 的开关。你想想看,一个 ls 命令,背后可能对应着 CONFIG_LS 这个宏。编译时,Makefile 根据这个宏决定要不要把 ls.c 编译进去。
配置层级大致是这样的:
- 顶层菜单:Settings、Archival、Coreutils、Console Utilities 等大类
- 子菜单:每个大类下面细分具体命令,比如 Coreutils 下面有 cat、chmod、cp、dd 等
- 子选项:每个命令还有自己的特性开关,比如 ls 是否支持颜色、是否支持长格式
我在项目中遇到过一种情况:明明把某个 applet 关了,编译出来的 BusyBox 还是包含它。后来发现是依赖关系没处理好——有些 applet 被别的模块隐式选中了。所以裁剪时,不光要看你想关什么,还得看它有没有被其他功能依赖。
核心原则:BusyBox 的配置是「白名单」机制。默认全开,你手动关掉不需要的。每关一个,镜像就小一点。
3.2 使用 menuconfig 进行裁剪
实际操作中,我习惯用 make menuconfig 来配置。它提供图形化界面,比直接改 .config 安全得多。你改错一个选项,编译可能直接挂掉。
基本流程如下:
- 解压 BusyBox 源码,进入目录
- 执行
make menuconfig - 逐级进入子菜单,关闭不需要的 applet
- 保存退出,生成新的
.config - 执行
make编译
嗯,这里要注意:menuconfig 依赖 ncurses 库。如果你的开发机上没有,先装一下:
sudo apt-get install libncurses5-dev libncursesw5-dev
进入菜单后,你会看到每个选项前面有 [ ]、< > 或 { } 符号。它们的含义是:
| 符号 | 含义 | 操作 |
|---|---|---|
[ ] |
二选一开关 | 按 Y 选中,按 N 取消 |
< > |
三态选择(编译/模块/不编译) | 按 Y/M/N 切换 |
{ } |
依赖选项,自动处理 | 一般不需要手动改 |
我个人习惯先做一次全编译,看看默认大小。然后对照项目需求,把用不上的 applet 逐个关掉。比如嵌入式设备上不需要 telnetd、ftpd、httpd 这些服务端程序,直接关掉能省不少空间。
小技巧:在 menuconfig 界面按 / 可以搜索 applet 名称。比如搜 "telnet",它会告诉你这个选项在哪个菜单下、当前状态是什么、依赖关系如何。裁剪时这个功能非常实用。
3.3 移除不需要的 applet
具体哪些 applet 可以移除?这得看你的产品定位。我做过一个 IoT 网关项目,只保留了以下命令:
- 文件操作:cat、cp、mv、rm、ls、mkdir、touch
- 系统工具:init、sh、mount、umount、reboot、poweroff
- 网络工具:ifconfig、ping、udhcpc、wget
- 文本处理:grep、sed、awk、cut、sort
- 其他:echo、printf、test、true、false
而下面这些,在大多数嵌入式场景下都可以关掉:
| applet | 说明 | 节省空间(约) |
|---|---|---|
| telnetd / ftpd / httpd | 服务端程序,嵌入式设备很少用 | 30-50KB |
| vi / ed | 文本编辑器,有 sed 就够了 | 80-120KB |
| find / xargs | 文件查找,小系统用不到 | 20-30KB |
| diff / patch | 文件比较,开发阶段才用 | 15-25KB |
| less / more | 分页查看,有 cat 就够了 | 10-20KB |
我曾经犯过一个错:把 sh 也关掉了,结果系统启动后直接 panic。因为 BusyBox 的 init 进程需要 shell 来执行启动脚本。所以裁剪时一定要搞清楚依赖链,别把核心组件砍了。
警告:永远不要移除 init、sh、mount、reboot 这四个 applet。它们是系统启动和运行的基础。移除了,你的 rootfs 大概率起不来。
3.4 静态编译与动态编译的选择
这是个大话题。很多新手会问:到底用静态编译还是动态编译?我的回答是:看你的 rootfs 里有没有 C 库。
两种方式的区别:
| 对比项 | 静态编译 | 动态编译 |
|---|---|---|
| BusyBox 体积 | 较大(约 800KB-1.2MB) | 较小(约 300KB-500KB) |
| 依赖 | 无外部依赖 | 需要 libc.so |
| 启动速度 | 稍快(无需加载动态库) | 稍慢(需要动态链接) |
| 升级维护 | 升级 BusyBox 需重新编译 | 可单独升级 libc |
| 适用场景 | 极小系统、initramfs | 有完整 rootfs 的系统 |
我个人建议:如果你的 rootfs 总大小在 8MB 以下,用静态编译。因为动态编译虽然 BusyBox 小了,但你还得把 libc.so 放进去。glibc 的 .so 文件动辄 1.5MB,加上去反而更大。如果用 uclibc 或 musl,动态库大概 300-500KB,这时候动态编译就有优势了。
配置方法很简单:
# 在 menuconfig 中进入 Settings
# 找到 "Build static binary (no shared libs)"
# 按 Y 选中,就是静态编译
# 按 N 取消,就是动态编译
我记得有一次做 4MB Flash 的项目,rootfs 空间非常紧张。我选择了静态编译 + musl 库,BusyBox 编译出来只有 600KB。但注意,musl 对某些 BusyBox 功能支持不完整,比如 nslookup 在 musl 下可能有问题。所以选库的时候要测试一下。
经验之谈:如果你不确定选哪个,先用动态编译。因为动态编译的 BusyBox 可以配合不同的 libc 版本使用,灵活性更高。等确定 rootfs 空间不够了,再切回静态编译。
3.5 裁剪流程总览
说了这么多,我画了一张图帮你理清整个裁剪流程。从源码到最终镜像,每一步都有对应的操作和检查点。
这张图把整个流程串起来了。你从源码开始,经过 menuconfig 裁剪,选择编译方式,然后编译、检查大小,最后集成到 rootfs 里测试。如果发现空间还是不够,就回到 menuconfig 继续裁剪,直到满足要求为止。
嗯,最后说一句:裁剪不是一次性的活。项目初期你可能不知道哪些命令是必需的,先留一些冗余。等系统跑起来后,通过 busybox --list 查看实际用到了哪些命令,再回头裁剪。这样更稳妥。