OTA升级

2018-04-18Google_A/B(无缝)系统更新概览-2

2018-04-19  本文已影响31人  uin_sisyphus

A/B(无缝)系统更新

A/B 系统更新(也称为无缝更新)的目标是确保在无线下载 (OTA) 更新期间在磁盘上保留一个可正常启动和使用的系统。采用这种方式可以降低更新之后设备无法启动的可能性,这意味着用户需要将设备送到维修和保修中心进行更换和刷机的情况将会减少。其他某些商业级操作系统(例如 ChromeOS)也成功运用了 A/B 更新机制。

要详细了解 A/B 系统更新,请参见分区选择(槽位)一节。

A/B 系统更新可带来以下好处:

关于 A/B 系统更新

进行 A/B 更新时,客户端和系统都需要进行更改。不过,OTA 更新包服务器应该不需要进行更改:更新包仍通过 HTTPS 提供。对于使用 Google OTA 基础架构的设备,系统更改全部是在 AOSP 中进行,并且客户端代码由 Google Play 服务提供。不使用 Google OTA 基础架构的原始设备制造商 (OEM) 将能够重复使用 AOSP 系统代码,但需要自行提供客户端。

如果 OEM 自行提供客户端,客户端需要:

客户端可能会:

在系统方面,A/B 系统更新会影响以下各项:

注意:只有对于新设备,才建议通过 OTA 实现 A/B 系统更新。

分区选择(槽位)

A/B 系统更新使用两组称为槽位(通常是槽位 A 和槽位 B)的分区。系统从“当前”槽位运行,但在正常操作期间,运行中的系统不会访问未使用的槽位中的分区。这种方法通过将未使用的槽位保留为后备槽位,来防范更新出现问题:如果在更新期间或更新刚刚完成后出现错误,系统可以回滚到原来的槽位并继续正常运行。为了实现这一目标,当前槽位使用的任何分区(包括只有一个副本的分区)都不应在 OTA 更新期间进行更新。

每个槽位都有一个“可启动”属性,该属性用于表明相应槽位存储的系统正确无误,设备可从相应槽位启动。系统运行时,当前槽位处于可启动状态,但另一个槽位则可能包含旧版本(仍然正确)的系统、包含更新版本的系统,或包含无效的数据。无论当前槽位是哪一个,都有一个槽位是活动槽位(引导加载程序在下次启动时将使用的槽位,也称为首选槽位)。

此外,每个槽位还都有一个由用户空间设置的“成功”属性,仅当相应槽位处于可启动状态时,该属性才具有相关性。被标记为成功的槽位应该能够自行启动、运行和更新。未被标记为成功的可启动槽位(多次尝试使用它启动之后)应由引导加载程序标记为不可启动,其中包括将活动槽位更改为另一个可启动的槽位(通常是更改为在尝试启动到新的活动槽位之前正在运行的槽位)。关于相应接口的具体详细信息在[ boot_control.h](https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/boot_control.h) 中进行了定义。

更新引擎守护进程

A/B 系统更新过程会使用名为 update_engine 的后台守护进程来使系统做好准备,以启动到更新后的新版本。该守护进程可以执行以下操作:

由于 update_engine 守护进程本身不会参与到启动流程中,因此该守护进程在更新期间可执行的操作受限于当前槽位中的 SELinux 政策和功能(在系统启动到新版本之前,此类政策和功能无法更新)。为了维持一个稳定可靠的系统,更新流程不应修改分区表、当前槽位中各个分区的内容,以及无法通过恢复出厂设置擦除的非 A/B 分区的内容。

更新引擎源代码

update_engine 源代码位于 [system/update_engine](https://android.googlesource.com/platform/system/update_engine/) 中。A/B OTA dexopt 文件分开放到了 installd 和一个程序包管理器中:

如需实际示例,请参阅 [/device/google/marlin/device-common.mk](https://android.googlesource.com/device/google/marlin/+/nougat-dr1-release/device-common.mk)

更新引擎日志

对于 Android 8.x 及更低版本,可在 logcat 及错误报告中找到 update_engine 日志。要使 update_engine 日志可在文件系统中使用,请将以下更改添加到您的细分版本中:

这些更改会将最新的 update_engine 日志的副本保存到 /data/misc/update_engine_log/update_engine.<var style="box-sizing: inherit; color: rgb(236, 64, 122); -webkit-font-smoothing: auto; font-weight: 700;">YEAR</var>-<var style="box-sizing: inherit; color: rgb(236, 64, 122); -webkit-font-smoothing: auto; font-weight: 700;">TIME</var>。除当前日志以外,最近的五个日志也会保存在 /data/misc/update_engine_log/ 下方。拥有日志组 ID 的用户将能够访问相应的文件系统日志。

引导加载程序交互

boot_control HAL 供 update_engine(可能还有其他守护进程)用于指示引导加载程序从何处启动。常见的示例情况及其相关状态包括:

流式更新支持

用户设备并非在 /data 上总是有足够的空间来下载更新包。由于 OEM 和用户都不想浪费 /cache 分区上的空间,因此有些用户会因为设备上没有空间来存储更新包而不进行更新。为了解决这个问题,Android 8.0 中添加了对流式 A/B 更新(下载数据块后直接将数据块写入 B 分区,而无需将数据块存储在 /data 上)的支持。流式 A/B 更新几乎不需要临时存储空间,并且只需要能够存储大约 100KiB 元数据的存储空间即可。

要在 Android 7.1 中实现流式更新,请选择以下补丁程序:

无论是使用 Google 移动服务 (GMS),还是使用任何其他更新客户端,都需要安装这些补丁程序,才能在 Android 7.1 中支持流式传输 A/B 更新包。

A/B 更新过程

当有 OTA 更新包(在代码中称为有效负载)可供下载时,更新流程便开始了。设备中的政策可以根据电池电量、用户活动、充电状态或其他政策来延迟下载和应用有效负载。此外,由于更新是在后台运行,因此用户可能并不知道正在进行更新。所有这些都意味着,更新流程可能随时会由于政策、意外重新启动或用户操作而中断。

OTA 更新包本身所含的元数据可能会指示可进行流式更新,在这种情况下,相应更新包也可采用非流式安装方式。服务器可以利用这些元数据告诉客户端正在进行流式更新,以便客户端正确地将 OTA 移交给 update_engine。如果设备制造商具有自己的服务器和客户端,便可以通过确保以下两项来实现流式更新:确保服务器能够识别出更新是流式更新(或假定所有更新都是流式更新),并确保客户端能够正确调用 update_engine 来进行流式更新。制造商可以根据更新包是流式更新变体这一事实向客户端发送一个标记,以便在进行流式更新时触发向框架端的移交工作。

有可用的有效负载后,更新流程将遵循如下步骤:

| 步骤 | 操作 |
| 1 | 通过 markBootSuccessful() 将当前槽位(或“源槽位”)标记为成功(如果尚未标记)。 |
| 2 | 调用函数 setSlotAsUnbootable(),将未使用的槽位(或“目标槽位”)标记为不可启动。当前槽位始终会在更新开始时被标记为成功,以防止引导加载程序回退到未使用的槽位(该槽位中很快将会有无效数据)。如果系统已做好准备,可以开始应用更新,那么即使其他主要组件出现损坏(例如界面陷入崩溃循环),当前槽位也会被标记为成功,因为可以通过推送新软件来解决这些问题。

更新有效负载是不透明的 Blob,其中包含更新到新版本的指示。更新有效负载由以下部分组成:

|
| 3 | 下载有效负载元数据。 |
| 4 | 对于元数据中定义的每项操作,都将按顺序发生以下行为:将相关数据(如果有)下载到内存中、应用操作,然后释放关联的内存。 |
| 5 | 对照预期的哈希重新读取并验证所有分区。 |
| 6 | 运行安装后步骤(如果有)。如果在执行任何步骤期间出现错误,则更新失败,系统可能会通过其他有效负载重新尝试更新。如果上述所有步骤均已成功完成,则更新成功,系统会执行最后一个步骤。 |
| 7 | 调用 setActiveBootSlot(),将未使用的槽位标记为活动槽位。将未使用的槽位标记为活动槽位并不意味着它将完成启动。如果引导加载程序(或系统本身)未读取到“成功”状态,则可以将活动槽位切换回来。 |
| 8 | 安装后步骤(如下所述)包括从“新更新”版本中运行仍在旧版本中运行的程序。如果此步骤已在 OTA 更新包中定义,则为强制性步骤,且程序必须返回并显示退出代码 0,否则更新失败。 |
| 9 | 在系统足够深入地成功启动到新槽位并完成重新启动后检查之后,系统会调用 markBootSuccessful(),将现在的当前槽位(原“目标槽位”)标记为成功。 |

注意:第 3 步和第 4 步占用了大部分更新时间,因为这两个步骤涉及写入和下载大量数据,并且可能会因政策或重新启动等原因而中断。

安装后

对于定义了安装后步骤的每个分区,update_engine 都会将新分区装载到特定位置,并执行与装载的分区相关的 OTA 中指定的程序。例如,如果安装后程序被定义为相应系统分区中的 usr/bin/postinstall,则系统会将未使用槽位中的这个分区装载到一个固定位置(例如 /postinstall_mount),然后执行 /postinstall_mount/usr/bin/postinstall命令。

为确保成功执行安装后步骤,旧内核必须能够:

例如,您可以使用 shell 脚本作为安装后程序(由旧系统中顶部包含 #! 标记的 shell 二进制文件解析),然后从新环境设置库路径,以便执行更复杂的二进制安装后程序。或者,您可以从专用的较小分区执行安装后步骤,以便主系统分区中的文件系统格式可以得到更新,同时不会产生向后兼容问题或引发 stepping-stone 更新;这样一来,用户便可以从出厂映像直接更新到最新版本。

新的安装后程序将受旧系统中定义的 SELinux 政策限制。因此,安装后步骤适用于在指定设备上执行设计所要求的任务或其他需要尽可能完成的任务(例如,更新支持 A/B 更新的固件或引导加载程序、为新版本准备数据库副本,等等)。安装后步骤不适用于重新启动之前的一次性错误修复(此类修复需要无法预见的权限)。

所选的安装后程序在 postinstall SELinux 环境中运行。新装载的分区中的所有文件都将带有 postinstall_file 标记,无论在重新启动到新系统后它们的属性如何,都是如此。在新系统中对 SELinux 属性进行的更改不会影响安装后步骤。如果安装后程序需要额外的权限,则必须将这些权限添加到安装后环境中。

重新启动后

重新启动后,update_verifier 会触发利用 dm-verity 进行完整性检查。系统会先启动该检查,然后再启动 zygote,以避免 Java 服务进行任何无法撤消且会导致无法进行安全回滚的更改。在此过程中,如果验证启动功能或 dm-verity 检测到任何损坏,引导加载程序和内核还可能会触发重新启动。检查完成后,update_verifier 会将启动标记为成功。

update_verifier 只会读取 /data/ota_package/care_map.txt(在使用 AOSP 代码时,该文件会包含在 A/B OTA 更新包中)中列出的数据块。Java 系统更新客户端(例如 GmsCore)会在重新启动设备前提取 care_map.txt 并设置访问权限,在系统成功启动到新版本后会删除所提取的文件。

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

上次更新日期:四月 3, 2018

上一篇 下一篇

猜你喜欢

热点阅读