4. 缓存机制概述
Yocto 构建之所以能在反复迭代中保持可接受的耗时,核心并不在于“编译得更快”,而在于“尽量不重复编译”。BitBake 通过多层缓存把已经完成的工作固化下来,让下一次构建直接复用结果。理解这些缓存的分工与命中条件,是后续做性能优化的前提。本节围绕共享状态缓存、下载缓存、哈希等价、签名与缓存命中原理展开。
一、四类缓存的分工
Yocto 的缓存不是单一机制,而是按“输入—中间产物—最终产物”分层协作的体系。可以把它理解为四条并行的复用通道:
- 下载缓存(DL_DIR):保存源码包、补丁、外部依赖的原始压缩包。只要 URL 与校验和一致,就不再访问网络。
- 共享状态缓存(SSTATE_DIR):保存每个任务(do_fetch、do_configure、do_compile、do_package 等)的输出快照,是加速的主力。
- 哈希等价(Hash Equivalence):当输入发生“语义无关”的变化时,通过等价服务器把新哈希映射到旧哈希,避免无谓重建。
- 签名(Signature):为每个任务计算唯一指纹,决定缓存能否命中,是整个缓存体系的判定依据。
二、签名与缓存命中原理
BitBake 为每个任务计算两类签名:basehash 与 taskhash。basehash 由任务依赖的变量集合(如 SRC_URI、PV、CFLAGS、do_configure 的输入变量)决定;taskhash 则在 basehash 基础上叠加任务依赖链上所有上游任务的哈希。最终 sstate 文件名就是 taskhash 的编码结果。
# 查看某任务的签名与 sstate 文件名
bitbake -S none core-image-minimal
# 输出示例(tmp/stamps/...)
do_compile.sigdata.3f9a1c7e...
do_package.sigdata.8b2d4e0a...
# 查看签名差异,定位为何未命中
bitbake-diffsigs \
tmp/stamps/.../do_compile.sigdata.old \
tmp/stamps/.../do_compile.sigdata.new
当签名一致时,BitBake 会去 SSTATE_DIR 或远端 sstate 镜像查找同名文件,命中则直接解包到工作目录,跳过该任务执行。若未命中,则执行任务并把结果写回缓存,供后续构建使用。
三、哈希等价:让“无关变化”不触发重建
实践中大量重建并非来自真实逻辑变化,而是来自路径、时间戳、主机名、绝对路径等“语义无关”的输入差异。哈希等价服务器(hash equivalence server)记录“不同输入哈希但输出等价”的映射关系。当某任务在新环境下算出新哈希,但服务器确认其输出与旧哈希一致时,会返回旧哈希,从而复用已有 sstate。
# local.conf 中启用哈希等价
BB_HASHSERVE = "auto"
BB_SIGNATURE_HANDLER = "OEEquivHash"
# 使用公共等价服务器(示例)
BB_HASHSERVE = "hashserv.yoctoproject.org:8686"
四、下载缓存与 sstate 的协作流程
下面这张图展示了从源码获取到任务复用的完整数据流,以及四类缓存各自介入的位置。
五、典型场景与命中率对比
下表列出常见操作对缓存命中率的影响,便于在优化时判断“该动哪里”。
| 操作 | 影响的缓存层 | 命中率变化 | 建议 |
|---|---|---|---|
| 修改配方中的 SRC_URI | 下载缓存 + 签名 | 该配方全链路失效 | 确认是否真的需要改源 |
| 仅修改 do_install 脚本 | taskhash | do_install 及下游失效 | 上游编译仍可复用 |
| 更换构建机工作目录 | basehash | 可能大面积失效 | 启用哈希等价服务器 |
| 清理 tmp 但保留 SSTATE_DIR | sstate 缓存 | 基本不受影响 | 推荐做法,避免全量重建 |
| 修改 DISTRO_FEATURES | 全局签名 | 大量配方失效 | 尽量在项目初期确定 |
六、常见误区与复盘
复盘时建议固定一套排查动作:先用 bitbake -S none 生成签名,再用 bitbake-diffsigs 对比新旧签名,定位是哪个变量或依赖发生了变化;随后判断该变化是否属于“语义无关”,若是,则通过哈希等价或变量归一化消除差异。把这一流程固化到 CI 中,缓存命中率会随迭代逐步提升,构建时间也会趋于稳定。