4、文件系统裁剪(下):移除不必要的文档和locale、使用sstrip/arm-strip进一步减小体积、合并重复的库文件、使用uclibc/musl替代glibc
上一节我们聊了文件系统裁剪的基础思路,比如删掉没用的二进制、清理缓存文件。今天咱们继续深入,把裁剪做到极致。说白了,嵌入式设备的存储空间就是寸土寸金,能省一点是一点。
我个人习惯把裁剪分成两个阶段:第一阶段是「粗剪」,去掉明显没用的东西;第二阶段是「精修」,用各种工具和技巧把每一字节都榨干。今天讲的就是精修阶段的核心操作。
移除不必要的文档和locale
你想想看,一个嵌入式设备跑在板子上,谁会去读man手册?谁会去切换系统语言?但默认的Buildroot或Yocto构建出来的文件系统,往往会带上大量文档和locale数据。这些东西加起来,轻松吃掉几MB甚至几十MB的空间。
我曾在项目中遇到过一件事:一个原本设计为8MB Flash的产品,固件死活塞不进去。排查了半天,发现光是/usr/share/locale目录就占了2.3MB。删掉之后,空间问题直接解决。
具体怎么做?我一般这么处理:
- 文档清理:删除
/usr/share/doc、/usr/share/man、/usr/share/info等目录。这些对运行时毫无用处。 - locale清理:只保留目标语言(比如
zh_CN或en_US),其余全部删除。或者干脆只保留C.UTF-8,这是最保险的做法。 - 其他无用数据:比如
/usr/share/zoneinfo里只保留需要的时区,/usr/share/misc里的magic文件也可以删掉。
经验之谈:在Buildroot中,可以通过配置BR2_TARGET_ROOTFS_TAR和BR2_PACKAGE_LOCALE相关选项来控制locale的生成。我建议直接关闭BR2_ENABLE_LOCALE_PURGE,然后手动指定保留的locale列表。
使用sstrip/arm-strip进一步减小体积
strip工具大家应该不陌生,它能去掉ELF文件中的符号表和调试信息。但普通的strip还不够狠。这里我要介绍两个更猛的家伙:sstrip和arm-strip。
sstrip(super strip)是专门针对嵌入式场景设计的。它不仅能去掉符号表,还能把ELF文件中的节头表(section headers)也干掉。节头表在运行时根本不需要,只对链接器和调试器有用。去掉之后,文件体积能再缩小10%~20%。
举个例子,一个原本用strip处理过的busybox二进制是500KB,再用sstrip处理一遍,可能变成420KB。别小看这80KB,在嵌入式世界里,这就是巨大的胜利。
# 使用sstrip处理单个文件
sstrip /path/to/binary
# 批量处理整个目录
find /target/usr/bin -type f -executable -exec sstrip {} \;
注意:sstrip处理过的文件不能再被gdb调试,也不能再被strip。所以一定要在最终发布前做这一步。我曾经因为顺序搞反,导致调试阶段无法定位问题,折腾了一整天。
对于ARM平台,还有arm-strip工具。它是专门针对ARM架构优化的strip版本,能更好地处理ARM特有的ELF格式。如果你用的是ARM Cortex-A系列芯片,强烈建议用arm-strip代替通用的strip。
合并重复的库文件
你有没有注意过,文件系统里经常出现同一个库的多个版本?比如libcrypto.so.1.0.0和libcrypto.so.1.1同时存在。这是因为不同的应用程序依赖了不同版本的库。
但说实话,很多情况下这些库的ABI是兼容的。我们可以通过符号链接或硬链接来合并它们。更激进的做法是:用librsync或hardlink工具扫描整个文件系统,找出内容完全相同的文件,然后用硬链接代替。
# 使用hardlink工具合并重复文件
hardlink -t /target/usr/lib # 先预览哪些文件可以合并
hardlink /target/usr/lib # 执行合并
我曾在项目中用这个技巧,把/usr/lib目录从12MB压缩到了9MB。省出来的3MB,刚好够放一个新的功能模块。
小技巧:合并库文件时,一定要检查它们的SONAME是否一致。如果SONAME不同,说明ABI不兼容,强行合并会导致运行时崩溃。这个坑我踩过,教训深刻。
使用uclibc/musl替代glibc
glibc功能强大,但体积也大。一个完整的glibc加上locale数据,轻松超过2MB。对于嵌入式设备来说,这太奢侈了。
这时候,uclibc和musl就派上用场了。它们都是轻量级的C标准库实现,体积只有glibc的几分之一,同时保持了良好的兼容性。
| 特性 | glibc | uclibc | musl |
|---|---|---|---|
| 典型体积 | ~2MB | ~400KB | ~600KB |
| POSIX兼容性 | 完全 | 大部分 | 完全 |
| 动态链接支持 | 完善 | 有限 | 完善 |
| 适用场景 | 桌面/服务器 | 深度嵌入式 | 通用嵌入式 |
我个人更倾向于musl。为什么?因为它的代码质量高,安全审计严格,而且对标准C的兼容性比uclibc好得多。我在一个基于ARM Cortex-A7的项目中,把glibc换成musl后,文件系统整体体积减少了35%,而且所有应用程序都运行正常。
当然,切换C库不是小事。你需要重新编译整个工具链和所有应用程序。在Buildroot中,只需要修改配置:
# 在Buildroot中启用musl
BR2_TOOLCHAIN_BUILDROOT_MUSL=y
避坑指南:我曾经在一个项目里直接替换了glibc为musl,结果发现某个第三方库用了glibc特有的__progname变量,导致段错误。所以,切换前一定要做完整的兼容性测试,特别是那些闭源的二进制库。
知识体系总览
下面这张图总结了文件系统裁剪的核心逻辑,你可以把它当作操作指南:
这张图展示了裁剪的四个层次。每一层都能带来可观的体积缩减,但越往后操作越复杂,风险也越高。我的建议是:先做前两层,如果空间还不够,再考虑第三层和第四层。
好了,关于文件系统裁剪的内容就讲到这里。这些技巧我在多个项目中验证过,效果都很不错。你可以在自己的开发板上试试,看看能省出多少空间。
可可精品资料库