fastboot分区刷写
fastboot flash system system.img 看起来是一条命令,实际包含两个不同的动作:host 把镜像字节 送入设备的 download buffer,设备端再把这块 buffer 写入名为 system 的分区。分区名还可能被 解析成 system_a、system_b,而动态分区又可能要求切换到 userspace fastboot,也就是 fastbootd。如果只记住命令,不追踪这些 owner 和状态,刷写失败后就无法判断是镜像没有送到、 分区拒绝写入、slot 没切对,还是重启后的消费者没有使用新结果。
本文承接系统镜像结构中的镜像、分区和逻辑分区模型, 并使用ADB架构中的 host/device 边界来解释 fastboot。读者需要知道文件描述符、 USB/TCP transport 和基本的 A/B slot 概念;不需要先了解某个厂商 bootloader 的私有 oem 命令。 本文不展开 AVB 密码学、OTA payload 生成和厂商启动链,只解释 AOSP fastboot 对这些外部条件的 接口边界。 读完后,读者应能从 host 的 FlashPartition 追到 download 与 flash 两次响应,定位 slot 和逻辑 分区的所有者,并根据 DATA、INFO、OKAY、FAIL 判断失败发生在传输、写入还是重启消费阶段。
1. fastboot环境
Bootloader fastboot 通常由设备启动链提供,负责解锁、读取变量、下载数据和写入物理分区;它不等同 于已经启动 Android 的 shell。fastbootd 是 recovery 中的 userspace fastboot,能够使用 Android 用户空间的逻辑分区和 snapshot 实现。host 端的 fastboot 可执行文件对两者都使用同一套命令协议, 但能力变量和允许的分区不同。
fastboot getvar is-userspace 是区分两种环境的只读入口。host 的 is_userspace_fastboot() 查询 该变量;当 flashall 发现任务需要 userspace fastboot 时,reboot_to_userspace_fastboot() 发出 reboot fastboot,等待原 transport 断开,再重新打开 transport 并确认变量变为 yes。因此, “命令返回成功”与“设备已经重启并由另一个 fastboot 实例接管”是两个时刻。
2. Flash路径
host driver 的接口在 system/core/fastboot/fastboot_driver_interface.h。FlashPartition() 的 默认实现先调用 Download(),成功后才调用 Flash();这使 download buffer 成为设备端 flash 的 输入,而不是 host 文件本身。核心调用顺序如下:
源码文件:system/core/fastboot/fastboot_driver.cpp
相关函数/类型:FlashPartition
RetCode FastBootDriver::FlashPartition(const std::string& partition,
const std::vector<char>& data) {
RetCode ret;
if ((ret = Download(partition, data))) {
return ret;
}
return Flash(partition);
}
RetCode FastBootDriver::FlashPartition(const std::string& partition,
android::base::borrowed_fd fd, uint32_t size) {
RetCode ret;
if ((ret = Download(partition, fd, size))) {
return ret;
}
return Flash(partition);
}Download() 先发送 download:<8位十六进制大小>,等待设备返回 DATA,再写入固定数量的字节, 最后消费 OKAY 或 FAIL。设备端 FastbootDevice::ExecuteCommands() 从 transport 读取命令, 按冒号拆分参数并查找 command map;DownloadHandler 检查锁状态、大小格式和非零约束,分配 download_data(),发送 DATA 后由 HandleData(true, ...) 读取完整数据。
这里有两个容易混淆的消费者。DownloadHandler 消费的是 transport 字节并拥有 download buffer; FlashHandler 消费的是这个 buffer,并把数据交给物理或逻辑分区实现。只有第二个 OKAY 才说明 写入动作完成。设备断开、主机进程被取消或第二步失败时,不能把第一步的 OKAY 当成刷写成功。
3. 分区与 slot 选择
host 不直接假定 system 对应哪个实际名字。do_for_partitions() 先查询 has-slot:<partition>;slot 为 a、b 或 all 时,再把逻辑名称展开成带后缀的分区。没有 slot 的分区保持原名。get_current_slot() 和 get_slot_count() 的结果来自 getvar,所以 slot 的 owner 是设备的 boot control,而不是 host 的字符串变量。
| 查询 | owner | 对刷写计划的影响 | 生效时机 |
|---|---|---|---|
current-slot | bootloader/boot control HAL | 默认目标 slot | 查询返回时 |
slot-count | boot control | all 的展开范围 | 生成任务时 |
has-slot:name | 分区实现 | 是否追加 _a/_b | 解析分区时 |
is-logical:name | fastboot 分区实现 | 是否需要 fastbootd/resize | 执行任务前 |
snapshot-update-status | boot control HAL | 是否允许擦除或切 slot | 每次保护检查时 |
set_active 改变的是 boot control 的启动选择,并不会把已经写入的镜像复制到另一个 slot。设备 端 SetActiveHandler 还会拒绝 locked device、非法后缀、超出 slot 数量和 snapshot merge 期间的 切换;SNAPSHOTTED 状态允许切换,但会通过 INFO 警告可能取消 update。成功后 FastbootDevice 保存 active_slot_,后续同一 fastbootd 会优先使用它;真正的启动消费者要等到 重启后由 bootloader/first-stage init 读取。
4. flashall
flashall 不是把目录中的所有文件按字母序写入。FlashAllTool::Flash() 先读取 android-info.txt 检查设备要求,再决定 slot、取消已有 snapshot 状态并生成任务。默认 image list 路径先放 boot-critical 镜像,插入 UpdateSuperTask,再放普通 OS 镜像;如果可以优化 super, 则合并为 OptimizedFlashSuperTask,否则为逻辑分区加入 resize 任务。
fastboot-info.txt 存在时,CollectTasksFromFastbootInfo() 改为解析文件中的命令任务;不存在时 才回退到 image list。这个分支说明刷写包的描述文件也是输入,而不是注释。缺失必需镜像会在 AddFlashTasks() 中终止;标记为 optional 的镜像才会被跳过。
5. fastbootd写入
FlashHandler 是 fastbootd 写入的入口。它先拒绝 locked device,再调用 IsProtectedPartitionDuringMerge() 检查 snapshot merge 保护分区;逻辑分区存在时会清除该分区的 updated 标志,以取消旧 snapshot 对本次显式刷写的影响,最后调用分区写入实现。userdata 成功 擦除后还会执行 PostWipeData(),因此“写入成功”和“所有用户数据清理动作完成”仍是同一 handler 中的两个阶段。
UpdateSuperHandler 负责更新动态分区表,同样要求设备已解锁。SnapshotUpdateHandler 通过 boot control HAL 查询或取消 SNAPSHOTTED/MERGING 状态;它不用直接挂载 /metadata,目的是与 bootloader 看到的状态保持一致。merge 期间不能随意 flash、erase userdata/metadata/misc 或切换 slot,否则会破坏恢复所需的快照关系。
把 FlashHandler 的顺序压缩成伪代码,可以看到这些检查不是 host 的建议,而是设备端的硬边界:
伪代码:下面只保留控制顺序,不能替代 system/core/fastboot/device/commands.cpp 中的真实实现。
if (GetDeviceLockStatus()) return FAIL;
if (IsProtectedPartitionDuringMerge(device, partition)) return FAIL;
if (LogicalPartitionExists(device, partition)) CancelPartitionSnapshot(device, partition);
if (Flash(device, partition) < 0) return FAIL;
return OKAY;因此 host 即使没有提前查询锁状态,也不能绕过设备端拒绝;反过来,host 收到 FAIL 也不能只看 字符串判断原因,应该结合此前的 is-logical 和 snapshot-update-status 查询。INFO 是提示性 响应,不改变成功判定;DATA 只属于 download 阶段,不能被误读成分区已经写完。
6. 解锁、擦除与恢复
解锁是 bootloader 的安全策略,不是 host fastboot 可以绕过的参数。设备端 handler 统一读取 GetDeviceLockStatus();locked 状态下 download、flash、erase、set_active 和 update-super 会返回 FAIL。因此先执行 fastboot flashing unlock 只是改变设备策略,是否允许 后续写入由设备端再次判断,且解锁通常伴随数据擦除,不能把它当作无损准备步骤。
中断和失败要按已提交阶段处理:
| 现象 | 尚未确认的事情 | 只读的下一步 |
|---|---|---|
download 返回 FAIL | 没有可靠的完整镜像 buffer | 查询 getvar is-userspace、重新建立连接 |
download 成功、flash 失败 | buffer 已到达但分区未接受 | 查询 partition-size/type/is-logical 和设备日志 |
| merge 中拒绝 flash/erase | snapshot 仍被恢复流程拥有 | 查询 snapshot-update-status,不要强行擦除受保护分区 |
set_active 成功但无法启动 | boot control 已更新,不代表镜像可启动 | 进入 bootloader,读取 current-slot,必要时切回可启动 slot |
| fastbootd 重启后消失 | transport 已切换或系统未能进入 userspace fastboot | 重新枚举设备并查询 is-userspace |
恢复的关键是先重新观察状态,再决定是否重试。对已经成功提交的 flash 盲目重试,可能覆盖本来 可启动的 slot;对 merge 中的分区执行 erase,可能让 snapshot 无法合并。reboot bootloader、 reboot fastboot 和普通 reboot 的消费者不同,分别回到 bootloader、fastbootd 或系统启动链。
7. 刷写测试
源码测试把 host 任务规划与协议结果分开验证。fastboot_driver_test.cpp 用 mock transport 返回 INFO 后 OKAY,断言 driver 不丢失中间信息并正确结束;同一组测试还覆盖 GetVar 和 下载到文件描述符。它检查的是 driver 对响应序列的解释,不代表真实 USB 控制器在所有情况下都可靠。
task_test.cpp 给定 partition、slot 和 image list,断言生成的 FlashTask 顺序、driver 调用 参数以及 dynamic partition 的优化条件;fastboot_test.cpp 的 unaligned chunk fixture 则断言 支持与不支持的块大小走不同结果。这些测试能约束“任务如何形成”和“边界输入如何失败”,不能证明 某个厂商分区布局、AVB key 或真实产品 bootloader 的私有命令。
设备端实现的失败断言来自 handler 分支:非法 download 大小、locked device、slot 越界、merge 期间切换和受保护分区写入都必须返回 FAIL;成功路径分别返回 DATA、OKAY,状态提示使用 INFO。这些响应只表示协议层已接受任务,重启后是否真的由新镜像启动仍需要产品设备上的独立检查。
8. 恢复验证
以下命令按只读优先顺序检查环境;set_active 会改变启动状态,刷写、擦除和解锁会改写分区或 清除数据,应只在明确的测试设备和已有备份上执行:
fastboot devices
fastboot getvar current-slot
fastboot getvar is-userspace
fastboot getvar slot-count
fastboot getvar has-slot:system
fastboot getvar is-logical:system
fastboot getvar snapshot-update-status读者可以据此复述一条源码链:哪一个 host 函数发出 download,哪一个 device handler 产生 DATA,哪个 owner 持有 slot,哪个 handler 在 merge 期间拒绝操作,以及重启后哪个消费者读取 结果。若设备允许并且风险可控,再用 fastboot set_active a/b 观察 current-slot 的变化, 最后用 fastboot reboot bootloader 或 fastboot reboot fastboot 验证环境切换;不要把这类实验 扩展到生产设备。
源码继续阅读可从 ADB架构 的 transport 边界回到 host/device 通道,再回到 系统镜像结构 对照静态、逻辑和 snapshot 分区。 本文未覆盖的 AVB、OTA payload 和厂商 oem 行为不属于上述 AOSP fastboot handler 的覆盖边界。
