14、OTA升级流程:update_engine、update_verifier、postinstall
各位好,今天我们来聊聊OTA升级流程中的三个关键角色:update_engine、update_verifier 和 postinstall。这三个组件,说白了就是升级过程中的「总指挥」、「质检员」和「装修队」。我在早期做系统开发时,经常把这三者的职责搞混,结果升级流程跑不通,还以为是内核出了问题。后来才明白,它们各司其职,缺一不可。
一、整体流程概览
先看一张我画的流程图,帮你快速建立整体认知:
嗯,这张图把整个流程分成了四个阶段。你会发现,update_engine 负责前期的下载和写入,update_verifier 负责校验,postinstall 负责安装后的收尾工作。重启之后,update_verifier 还会再出来检查一次,确保系统真的没问题。
二、update_engine:升级的总指挥
update_engine 是 A/B 系统升级的核心守护进程。我个人习惯把它理解为「升级调度器」——它不直接操作分区,而是协调所有升级动作。
2.1 主要职责
- 下载 OTA 包:从服务器拉取增量或完整包
- 解析 payload:读取 payload 中的元数据,确定要更新的分区
- 写入非活跃 slot:将新系统写入当前未使用的 slot(比如当前是 slot_a,就写 slot_b)
- 管理状态机:维护升级状态,支持断点续传
核心要点:update_engine 运行在用户空间,通过 binder 接口与系统服务通信。它不直接操作块设备,而是通过 libupdate_engine 库调用内核的 dm-verity 和 loop 设备。
2.2 状态机示例
我在项目中遇到过一个问题:升级到一半突然断电,重启后 update_engine 怎么知道该从哪继续?答案就在它的状态机里:
// 简化的状态机定义
enum UpdateState {
STATE_IDLE = 0,
STATE_CHECKING_FOR_UPDATE,
STATE_UPDATE_AVAILABLE,
STATE_DOWNLOADING,
STATE_DOWNLOADING_FINISHED,
STATE_VERIFYING,
STATE_UPDATING,
STATE_UPDATED_NEED_REBOOT,
STATE_ERROR,
};
每次状态变更,update_engine 都会把当前状态写入 /data/misc/update_engine/prefs 目录。重启后,它会读取这个文件,从断点处继续。说白了,这就是个「记笔记」的机制。
三、update_verifier:系统的质检员
update_verifier 这个名字容易让人误解——它不是在升级过程中做校验,而是在重启后做校验。为什么?
你想想看,升级过程中 update_engine 已经把数据写入了非活跃 slot,但那时候系统还在运行,没法验证新系统能不能正常启动。所以 Android 的设计思路是:先重启,让 bootloader 加载新系统,然后由 update_verifier 在 init 阶段执行校验。
3.1 校验什么
| 校验项 | 说明 | 失败后果 |
|---|---|---|
| dm-verity 哈希树 | 验证系统分区的完整性 | 回滚到旧 slot |
| 文件系统一致性 | 检查 ext4/f2fs 是否损坏 | 尝试修复,失败则回滚 |
| 关键服务可用性 | 确认核心服务能正常启动 | 触发 rescue 模式 |
注意:update_verifier 的校验是「软校验」——它不会阻塞系统启动,而是并行执行。如果校验失败,它会设置一个标志位,下次重启时 bootloader 会读取这个标志,自动回滚到旧 slot。
3.2 我曾经踩过的坑
我曾经遇到过一个问题:升级后系统能正常启动,但 update_verifier 报告 dm-verity 校验失败。查了半天,发现是 OTA 包生成时,哈希树计算用的块大小和实际分区不一致。嗯,这种问题特别隐蔽,因为系统能跑,但每次重启都会触发回滚。后来我养成了一个习惯:生成 OTA 包后,先用 avbtool verify_image 手动检查一遍哈希树。
四、postinstall:系统的装修队
postinstall 是升级流程的「收尾阶段」。它不是一个单独的进程,而是一组在 init.rc 中定义的脚本或二进制程序。update_engine 在写入新系统后,会调用 postinstall 来完成一些「脏活累活」。
4.1 典型任务
- 更新 bootloader 参数:比如修改
slot-suffix,告诉 bootloader 下次从新 slot 启动 - 清理缓存:删除旧的 dalvik-cache 或 odex 文件
- 更新 vendor 分区:有些厂商会把驱动放在 vendor 分区,postinstall 负责更新
- 执行厂商自定义脚本:比如修改 modem 固件、校准传感器等
个人建议:postinstall 脚本一定要设计成「幂等」的——也就是执行多次和一次效果相同。为什么?因为如果升级过程中断电,重启后 update_engine 可能会重新执行 postinstall。如果脚本不是幂等的,就可能出现重复写入、文件冲突等问题。
4.2 代码示例
一个典型的 postinstall 脚本长这样:
# /system/bin/postinstall.sh
#!/system/bin/sh
# 更新 slot 信息
echo "Updating slot suffix..."
echo -n "_b" > /misc/ota/slot-suffix
# 清理旧缓存
rm -rf /data/dalvik-cache/*
rm -rf /data/system/package_cache/*
# 执行厂商脚本
if [ -f /vendor/bin/postinstall_vendor.sh ]; then
/vendor/bin/postinstall_vendor.sh
fi
# 标记升级完成
setprop sys.ota.completed 1
exit 0
注意看最后一行 setprop sys.ota.completed 1——这个属性会被 init 进程监听,一旦设置为 1,init 就会触发重启。所以 postinstall 执行完毕后,系统会自动重启进入新 slot。
五、三者的协作关系
讲到这里,你应该能看出这三者的分工了:
- update_engine:在旧系统中运行,负责下载和写入新系统
- postinstall:在旧系统中运行(但由 update_engine 触发),负责收尾工作
- update_verifier:在新系统中运行(重启后),负责校验新系统是否可用
说白了,这是一个「先写后验」的流程。update_engine 写完了,postinstall 收尾了,然后重启,update_verifier 再检查。如果检查不过,就回滚。这种设计保证了升级的安全性——你永远不会启动一个损坏的系统。
核心总结:
- update_engine 是「写」的组件
- postinstall 是「收尾」的组件
- update_verifier 是「验」的组件
- 三者通过 slot 切换 和 属性系统 协同工作
嗯,这就是 OTA 升级流程中三个核心组件的全貌。实际项目中,你可能会遇到各种边界情况——比如升级过程中存储空间不足、postinstall 脚本执行超时、update_verifier 误报校验失败等等。但只要你理解了它们各自的职责和协作方式,排查问题就会变得有条理。
公众号:蓝海资料掘金营,微信deep3321