Android 10 引入了用户数据检查点 (UDC),它允许 Android 在 Android 无线 (OTA) 更新失败时回滚到之前的状态。借助 UDC,如果 Android OTA 更新失败,设备可以安全地回滚到之前的状态。虽然 A/B 更新解决了早期启动的此问题,但在用户数据分区(挂载在 /data
上)被修改时,不支持回滚。
UDC 使设备即使在用户数据分区被修改后也能恢复。UDC 功能通过以下方式实现此目的:文件系统的检查点功能;文件系统不支持检查点时的替代实现;与启动加载程序 A/B 机制集成,同时支持非 A/B 更新;以及支持密钥版本绑定和密钥回滚预防。
用户影响
UDC 功能改善了用户的 OTA 更新体验,因为在 OTA 更新失败时,丢失数据的用户更少。这可以减少遇到更新过程中出现问题的用户的支持电话数量。但是,当 OTA 更新失败时,用户可能会注意到设备多次重启。
工作原理
不同文件系统中的检查点功能
对于 F2FS 文件系统,UDC 将检查点功能添加到上游 4.20 Linux 内核,并将其反向移植到运行 Android 10 的设备支持的所有通用内核。
对于其他文件系统,UDC 使用名为 dm_bow
的设备映射器虚拟设备来实现检查点功能。dm_bow
位于设备和文件系统之间。当分区挂载时,会发出 trim 命令,导致文件系统在所有可用块上发出 trim 命令。dm_bow
拦截这些 trim 命令,并使用它们来设置可用块列表。然后,读取和写入将未经修改地发送到设备,但在允许写入之前,恢复所需的数据会备份到可用块。
检查点流程
当挂载带有 checkpoint=fs/block
标志的分区时,Android 会在驱动器上调用 restoreCheckpoint
,以允许设备恢复任何当前检查点。init
然后调用 needsCheckpoint
函数来确定设备是处于启动加载程序 A/B 状态还是已设置更新重试计数。如果任一条件为真,Android 会调用 createCheckpoint
以添加挂载标志或构建 dm_bow
设备。
分区挂载后,将调用检查点代码以发出 trim 命令。然后,启动过程照常继续。在 LOCKED_BOOT_COMPLETE
时,Android 会调用 commitCheckpoint
以提交当前检查点,并且更新照常继续。
管理 Keymaster 密钥
Keymaster 密钥用于设备加密或其他目的。为了管理这些密钥,Android 会延迟密钥删除调用,直到检查点提交。
监控运行状况
运行状况守护程序会验证是否有足够的磁盘空间来创建检查点。运行状况守护程序位于 cp_healthDaemon
中的 Checkpoint.cpp
中。
运行状况守护程序具有以下可配置的行为
ro.sys.cp_msleeptime
:控制设备检查磁盘使用情况的频率。ro.sys.cp_min_free_bytes
:控制运行状况守护程序查找的最小值。ro.sys.cp_commit_on_full
:控制当磁盘已满时,运行状况守护程序是重启设备还是提交检查点并继续。
检查点 API
UDC 功能使用检查点 API。有关 UDC 使用的其他 API,请参阅 IVold.aidl
。
void startCheckpoint(int retry)
创建检查点。
框架在准备好开始更新时调用此方法。检查点是在重新启动后挂载 R/W 的检查点文件系统(例如 userdata)之前创建的。如果重试计数为正数,则 API 会处理跟踪重试,并且更新程序会调用 needsRollback
以检查是否需要回滚更新。如果重试计数为 -1
,则 API 会听从 A/B 启动加载程序的判断。
在执行正常的 A/B 更新时,不会调用此方法。
void commitChanges()
提交更改。
框架在重新启动后,当更改准备好提交时调用此方法。这在数据(例如图片、视频、短信、服务器接收回执)写入 userdata 之前以及 BootComplete
之前调用。
如果不存在任何活动的检查点更新,则此方法无效。
abortChanges()
强制重启并恢复到检查点。放弃自首次重启以来的所有 userdata 修改。
框架在重新启动后但在 commitChanges
之前调用此方法。当调用此方法时,retry_counter
会减小。会生成日志条目。
bool needsRollback()
确定是否需要回滚。
在非检查点设备上,返回 false
。在检查点设备上,在非检查点启动期间返回 true
。
实现 UDC
参考实现
有关如何实现 UDC 的示例,请参阅 dm-bow.c。有关该功能的其他文档,请参阅 dm-bow.txt。
设置
在 init.hardware.rc
文件中的 on fs
中,确保您具有
mount_all /vendor/etc/fstab.${ro.boot.hardware.platform} --early
在 init.hardware.rc
文件中的 on late-fs
中,确保您具有
mount_all /vendor/etc/fstab.${ro.boot.hardware.platform} --late
在 fstab.hardware
文件中,确保 /data
标记为 latemount
。
/dev/block/bootdevice/by-name/userdata /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,sysfs_path=/sys/devices/platform/soc/1d84000.ufshc,reservedsize=128M,checkpoint=fs
添加元数据分区
UDC 需要元数据分区来存储非启动加载程序重试计数和密钥。设置元数据分区并在 /metadata
处提前挂载它。
在 fstab.hardware
文件中,确保 /metadata
标记为 earlymount
或 first_stage_mount
。
/dev/block/by-name/metadata /metadata ext4 noatime,nosuid,nodev,discard,sync wait,formattable,first_stage_mount
将分区初始化为全零。
将以下行添加到 BoardConfig.mk
BOARD_USES_METADATA_PARTITION := true BOARD_ROOT_EXTRA_FOLDERS := existing_folders metadata
更新系统
F2FS 系统
对于使用 F2FS 格式化数据的系统,请确保您的 F2FS 版本支持检查点。有关更多信息,请参阅不同文件系统中的检查点功能。
将 checkpoint=fs
标志添加到挂载在 /data
处的设备的 fstab 的 <fs_mgr_flags>
部分。
非 F2FS 系统
对于非 F2FS 系统,必须在内核配置中启用 dm-bow
。
将 checkpoint=block
标志添加到挂载在 /data
处的设备的 fstab 的 <fs_mgr_flags>
部分。
检查日志
调用检查点 API 时会生成日志条目。
验证
要测试您的 UDC 实现,请运行 VtsKernelCheckpointTest
VTS 测试集。