无线下载更新 (OTA) 是一种用于在 Fuchsia 上更新操作系统的机制。本文档详细介绍了 OTA 更新在 Fuchsia 上的运作方式。
更新流程分为以下阶段:
正在检查更新
操作系统更新过程的两个入口点是 omaha-client
和 system-update-checker
组件。
omaha-client
和 system-update-checker
的用途相同,即查明是否有操作系统更新并启动更新。
通常,如果产品要使用 Omaha 确定更新可用性,则应使用 omaha-client
。如果产品不想使用 Omaha,而是想直接从软件包代码库中检查更新,则应使用 system-update-checker
。
在任何指定的 Fuchsia 系统中,只能运行以下组件之一:
使用 omaha-client 更新检查
在启动过程中,omaha-client
会启动并开始定期检查更新。在检查过程中,omaha-client
会轮询 Omaha 服务器以检查是否有更新。
使用 Omaha 的好处包括:
- 它允许在一系列 Fuchsia 设备上按比例发布系统更新。例如,可以配置为仅更新所有设备群中的 10%。这意味着,在对 Omaha 进行轮询时,只有 10% 的设备会看到有可用更新。其余 90% 的设备将不会看到可用的更新。
- 它支持不同的更新渠道。例如,测试设备可以从测试渠道获取更新,并获取最新(可能不稳定)的软件。这样,正式版设备就可以从正式版渠道获取更新,并获得最稳定的软件。您可以选择将渠道信息连同产品和版本一起提供给 Omaha。
图 1. 使用 omaha-client
简化了更新检查流程。有一些政策用于控制 omaha-client
能否检查更新或应用更新。
omaha-client
从 Omaha 服务器获取更新软件包网址后,omaha-client
会告知 system-updater
启动更新。
使用 system-update-checker 更新检查
未使用omaha-client
的设备将使用system-update-checker
。system-update-checker
会定期轮询更新软件包,具体取决于其配置方式。如果未指定 auto_update
,则这些检查默认处于停用状态。
为了检查是否有可用更新,system-update-checker
会检查以下条件:
- 当前正在运行的系统映像(位于
/pkgfs/system/meta
中)的哈希值(位于更新软件包中)与系统映像的哈希值(位于packages.json
中)不同? - 如果系统映像没有变化,那么系统上当前运行的 vbmeta 是否与更新软件包的 vbmeta 不同?
- 如果没有 vbmeta,系统中当前运行的 ZBI 是否与更新软件包的 ZBI 不同?
如果答案为“是”,system-update-checker
就会知道更新软件包已更改。system-update-checker 意识到更新软件包已更改后,system-update-checker
会触发 system-updater
以使用默认更新软件包 (fuchsia-pkg://fuchsia.com/update) 启动更新。
图 2. 使用 system-update-checker
进行了更新检查流程的简化版本。
如果不需要更新,更新检查工具会保存它在服务器上看到的最新已知更新软件包。在后续检查更新时,系统会对照服务器上最近一次的已知哈希,对提取的更新软件包的哈希进行检查。如果最新更新软件包的哈希值自上次检查后发生了变化,更新检查工具会将正在运行的系统上的 vbmeta 和 ZBI 与更新软件包中的相应映像进行比较。如果正在运行的映像和更新软件包之间的 vbmeta 或 ZBI 不同,检查工具会启动系统更新。
监控
如果客户端想要监控更新进度和状态,则可以实现 fuchsia.update.AttemptsMonitor
协议,并提供客户端到 fuchsia.update.Manager
FIDL 协议的 MonitorAllUpdateChecks()
方法。只有当其他方法启动了更新,或者更新当前正在进行时,fuchsia.update.AttemptsMonitor
实例才会收到消息。这不会触发新的更新。
fuchsia.update.AttemptsMonitor
实例将收到 OnStart
消息,其中包含 fuchsia.update.Monitor
协议的服务器端。这样,客户端就可以接收和处理 OnState
消息,并告知更新状态更改。
另一种方法是实现 fuchsia.update.Monitor
并将客户端提供给 fuchsia.update.Manager
协议的 CheckNow()
方法。此操作将开始检查是否有更新。它只会监控当前正在运行的更新,并在更新完成后关闭句柄。
暂存更新
无论更新是由 omaha-client
、system-update-checker
还是强制更新检查触发的,都需要将更新写入磁盘。
更新流程分为以下几个步骤:
图 3. 设备目前正在运行假设的操作系统版本 1(在槽位 A 上),并开始更新到假设的操作系统版本 2(到槽位 B)。警告:实际上,这可能并不是磁盘的分区方式。
提取更新软件包
system-updater
使用提供的更新软件包网址获取更新软件包。然后,系统会更新动态索引以引用新的更新软件包。更新软件包示例可能如下所示:
/board
/epoch.json
/firmware
/fuchsia.vbmeta
/packages.json
/recovery.vbmeta
/version
/zbi.signed
/zedboot.signed
/meta/contents
/meta/package
如果提取因空间不足而失败,system-updater
将触发垃圾回收以删除静态或动态索引或保留的软件包集中未引用的所有 BLOB。垃圾回收后,system-updater
会重试提取。如果重试失败,system-updater
将仅将已设置的保留软件包替换成仅替换其尝试提取的更新软件包(如果更新软件包网址包含哈希,否则它将清除保留的软件包集),然后再次触发垃圾回收并重试更新软件包提取。
图 4. system-updater
会指示 pkg-resolver
解析版本 2 更新软件包。我们假设 system-updater
因空间不足而未能提取更新软件包,触发了垃圾回收以逐出槽位 B 引用的版本 0 blob,然后重新尝试成功提取版本 2 更新软件包。
(可选)更新软件包可以包含 update-mode
文件。此文件确定系统更新是在“正常”还是“强制恢复”模式下进行。如果更新模式文件不存在,system-updater
会默认为普通模式。
当模式为 ForceRecovery 时,system-updater
会将映像写入 recovery 分区,并将槽 A 和 B 标记为不可启动,然后启动到 recovery。如需了解详情,请参阅 ForceRecovery 的实现。
验证面板是否匹配
当前正在运行的系统具有位于 /config/build-info/board
中的板级文件。system-updater
会验证系统上的板级文件是否与更新软件包中的板级文件相匹配。
图 5. system-updater
用于验证更新软件包中的开发板是否与插槽 A 中的开发板一致。
验证系统是否支持纪元
更新软件包包含一个周期文件 (epoch.json
)。如果更新软件包的周期(目标周期)小于 system-updater
的周期(源周期),则 OTA 失败。如需了解其他背景信息,请参阅 RFC-0071。
图 6. system-updater
会将更新软件包中的周期与当前操作系统的周期进行比较,以验证支持该更新软件包中的周期。
替换保留的软件包集
替换使用当前更新软件包设置的保留软件包,以及稍后将在 OTA 流程中提取的所有软件包。
保留的软件包集是一组防止垃圾回收的软件包(除了静态和动态索引中的软件包之外)。它用于防止垃圾回收删除当前更新进程所需的 BLOB。例如,假设某台设备提取了更新所需的一些软件包,然后因不相关的原因而重新启动。当设备再次开始 OTA 时,它仍然需要在重新启动之前提取的软件包,但这些软件包不受动态索引保护(与设置的保留软件包一样,系统会在重新启动时清除动态索引)。通过将这些软件包添加到保留的软件包集,system-updater
可以触发垃圾回收(例如,移除先前系统版本使用的 blob),而无需撤消过去的工作。
触发垃圾回收
系统会触发垃圾回收,以删除旧系统独有的所有 BLOB。此步骤为新软件包释放更多空间。
图 7. system-updater
指示 pkg-cache
对旧系统独有的所有 BLOB 进行垃圾回收。在此示例中,这意味着 pkg-cache
将逐出版本 1 更新软件包独占的 BLOB。
提取其余软件包
系统更新程序会解析更新软件包中的 packages.json
文件。packages.json
如下所示:
{
"version": “1”,
"content": [
"fuchsia-pkg://fuchsia.com/sshd-host/0?hash=123..abc",
"fuchsia-pkg://fuchsia.com/system-image/0?hash=456..def"
...
]
}
system-updater
会指示 pkg-resolver
解析所有软件包网址。解析软件包时,软件包管理系统仅提取更新所需的 BLOB,即仅获取尚不存在的 BLOB。软件包管理系统会提取整个 BLOB,而不是系统上当前可能存在的 diff(差异比较)对象。
提取所有软件包后,会触发 BlobFS 同步以将 BLOB 清空到永久性存储空间。此过程可确保 BlobFS 中提供了系统更新所需的所有 BLOB。
图 8. system-updater
会指示 pkg-resolver 解析 packages.json
中引用的版本 2 软件包。
将图片写入块存储设备
system-updater
确定需要将哪些映像写入块存储设备。映像分为两种:资源和固件。
然后,system-updater
会指示打包器写入引导加载程序和固件。
为了防止闪存磨损,只有当该映像与块设备上已存在的映像不同时,系统才会将该映像写入分区。
然后,system-updater
会指示摊开器写入 Fuchsia ZBI 及其 vbmeta。
否则,投放器会将其同时写入 A 分区和 B 分区(如果存在 B 分区)。
最后,system-updater
会指示摊开器写入恢复 ZBI 及其 vbmeta。
图 9. system-updater
通过摊铺器将版本 2 的映像写入槽 B。
将备用分区设置为活跃分区
如果设备支持 ABR,system-updater
会使用 paver 将备用分区设置为活跃分区。这样,设备就会在下次启动时启动到备用分区。
您可以通过多种方式引用槽状态。例如,内部铺垫程序使用 Successful
,而 FIDL 服务使用 Healthy
,而其他情形则可能使用“Active”“已弃用”“Bootable”“Unbootable”“Current”、“Alternate”等。
重要的元数据是为每个内核插槽存储的 3 条信息。此信息有助于确定每个内核插槽的状态。例如,在槽位 B 标记为活动槽位之前,元数据可能如下所示:
元数据 | 槽 A | 广告位 B |
---|---|---|
优先级 | 15 | 0 |
剩余尝试次数 | 0 | 0 |
健康* | 1 | 0 |
将槽 B 标记为活动后,元数据将如下所示:
元数据 | 槽 A | 广告位 B |
---|---|---|
优先级 | 14 | 15** |
剩余尝试次数 | 0 | 7** |
健康餐饮 | 1 | 0 |
如果设备不支持 ABR,系统会跳过此检查,因为没有备用分区。不过,每次更新都会写入一个活跃分区。
图 10. system-updater
将槽位 B 设置为“Active”,以便设备在下次启动时启动到槽位 B。
重新启动
根据更新配置,设备不一定会重新启动。设备重新启动后,会启动到新的插槽中。
图 11. 设备重新启动到插槽 B 中并开始运行版本 2。
验证更新
一旦系统验证了更新,系统就会提交更新。
系统通过以下方式验证更新:
重新启动进入更新后的版本
下次启动时,引导加载程序需要确定启动到哪个槽位。在本例中,引导加载程序确定启动到槽位 B,因为槽位 B 的优先级更高,并且剩余的尝试次数超过 0 次(请参阅将备用分区设置为活动分区)。然后,引导加载程序会验证 B 的 ZBI 是否与 B 的 vbmeta 相匹配,并最终启动到槽 B 中。
前期启动后,fshost
使用新的系统映像软件包启动 pkgfs
。这是在预演更新时 packages.json
中引用的系统映像软件包。系统映像软件包中包含 static_packages
文件,其中列出了新系统的基础软件包。例如:
pkg-resolver/0 = new-version-hash-pkg-resolver
foo/0 = new-version-hash-foo
bar/0 = new-version-hash-bar
...
// Note the system-image package is not referenced in static_packages
// because it's impossible for it to refer to its own hash.
然后,pkgfs
会将所有这些软件包作为基础软件包加载。软件包显示在 /pkgfs/{packages, versions}
中,这表示软件包已安装或激活。然后,系统启动 pkg-resolver
、pkg-cache
、netstack
等...
提交更新
system-update-committer
组件会运行各种检查来验证新更新是否成功。例如,它指示 BlobF 任意读取 1 MiB 的数据。如果系统在启动时已提交系统,会跳过这些检查。如果检查失败,并且根据系统的配置方式,system-update-committer
可能会触发重新启动。
验证更新后,当前分区(槽 B)会被标记为 Healthy
。使用将备用分区设置为活跃中所述的示例,启动元数据现在可能如下所示:
元数据 | 槽 A | 广告位 B |
---|---|---|
优先级 | 14 | 15 |
剩余尝试次数 | 7 | 0 人 |
健康餐饮 | 0 人 | 1 |
然后,备用分区(槽位 A)将被标记为不可启动。现在,启动元数据可能如下所示:
元数据 | 槽 A | 广告位 B |
---|---|---|
优先级 | 0 人 | 15 |
剩余尝试次数 | 0 人 | 0 |
健康餐饮 | 0 | 1 |
之后,更新将被视为已提交。这意味着:
- 在下一次系统更新之前,系统始终启动到槽位 B 中。
- 系统会停止启动到槽位 A,直到下一次系统更新覆盖槽位 A。
- 现在可对槽位 A 引用的 BLOB 进行垃圾回收。
- 现在允许进行后续系统更新。当更新检查工具发现新的更新时,整个更新过程会再次开始。