4. 缓存机制概述

Yocto 构建之所以能在反复迭代中保持可接受的耗时,核心并不在于“编译得更快”,而在于“尽量不重复编译”。BitBake 通过多层缓存把已经完成的工作固化下来,让下一次构建直接复用结果。理解这些缓存的分工与命中条件,是后续做性能优化的前提。本节围绕共享状态缓存、下载缓存、哈希等价、签名与缓存命中原理展开。

一、四类缓存的分工

Yocto 的缓存不是单一机制,而是按“输入—中间产物—最终产物”分层协作的体系。可以把它理解为四条并行的复用通道:

  • 下载缓存(DL_DIR):保存源码包、补丁、外部依赖的原始压缩包。只要 URL 与校验和一致,就不再访问网络。
  • 共享状态缓存(SSTATE_DIR):保存每个任务(do_fetch、do_configure、do_compile、do_package 等)的输出快照,是加速的主力。
  • 哈希等价(Hash Equivalence):当输入发生“语义无关”的变化时,通过等价服务器把新哈希映射到旧哈希,避免无谓重建。
  • 签名(Signature):为每个任务计算唯一指纹,决定缓存能否命中,是整个缓存体系的判定依据。
缓存命中的本质是一句话:任务签名相同,则输出可复用;签名不同,则必须重算。下载缓存、sstate 缓存、哈希等价都只是围绕这句话的不同实现手段。

二、签名与缓存命中原理

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"
哈希等价对 CI 场景收益最大:不同构建机、不同工作目录、不同用户路径产生的哈希差异,可以被等价服务器“归一化”,显著提升跨机器缓存命中率。

四、下载缓存与 sstate 的协作流程

下面这张图展示了从源码获取到任务复用的完整数据流,以及四类缓存各自介入的位置。

下载缓存 DL_DIR 源码包 / 补丁 / 外部依赖 签名计算 basehash + taskhash 哈希等价服务器 等价映射 / 归一化哈希 缓存命中判定 taskhash 是否已存在? 命中:解包 sstate 跳过任务执行 直接进入下游任务 未命中:执行任务 编译 / 打包 / 安装 结果写回 SSTATE_DIR 是 否 共享状态缓存 SSTATE_DIR / 远端 sstate 镜像

五、典型场景与命中率对比

下表列出常见操作对缓存命中率的影响,便于在优化时判断“该动哪里”。

操作 影响的缓存层 命中率变化 建议
修改配方中的 SRC_URI 下载缓存 + 签名 该配方全链路失效 确认是否真的需要改源
仅修改 do_install 脚本 taskhash do_install 及下游失效 上游编译仍可复用
更换构建机工作目录 basehash 可能大面积失效 启用哈希等价服务器
清理 tmp 但保留 SSTATE_DIR sstate 缓存 基本不受影响 推荐做法,避免全量重建
修改 DISTRO_FEATURES 全局签名 大量配方失效 尽量在项目初期确定

六、常见误区与复盘

误区一:把 DL_DIR 当成 sstate 缓存。DL_DIR 只保存源码包,不保存编译产物。清空 DL_DIR 会导致重新下载,但不会直接导致重新编译;清空 SSTATE_DIR 才会引发大规模重建。
误区二:认为“改了注释也会重建”。BitBake 的签名只跟踪被显式声明为依赖的变量。若注释未进入变量值,通常不会影响签名;但若注释位于被 checksum 的变量中,则可能触发重建。
误区三:忽略哈希等价服务器的部署。在多机 CI 中,不部署等价服务器会让每台机器各自为战,缓存命中率大幅下降,构建时间成倍增加。

复盘时建议固定一套排查动作:先用 bitbake -S none 生成签名,再用 bitbake-diffsigs 对比新旧签名,定位是哪个变量或依赖发生了变化;随后判断该变化是否属于“语义无关”,若是,则通过哈希等价或变量归一化消除差异。把这一流程固化到 CI 中,缓存命中率会随迭代逐步提升,构建时间也会趋于稳定。