📺 B站:博主个人介绍
📘 博主书籍-京东购买链接*:Yocto项目实战教程
📘 加博主微信,进技术交流群: jerrydev
Jetson AGX Orin 本地 OTA 全流程解析与实战总结(A/B + _alt + UEFI Capsule)
适用范围:Jetson AGX Orin(T234),L4T/JetPack R36.x 系列 local/offline OTA(
nv_ota_start.sh+ payload 包)。文档定位:面向工程落地的“过程复盘 + 机制解释 + 排障清单”。全篇采用客观描述,不使用第一人称。
1. 背景与目标
在 Jetson 平台上,本地 OTA 的典型目标是:
- 在设备本地执行
nv_ota_start.sh <ota_payload_package.tar.gz> - 将新版本写入 非当前运行槽位(A/B Rootfs 模型)
- 使用 UEFI Capsule 触发 bootloader 延迟更新(重启阶段执行)
- 通过状态变量/链路计数完成切槽与回滚保护
实践中,最常见的困惑集中在两点:
- “日志看起来更新了很多分区,是否等于 A/B 两边都被更新?”
- “bootloader 为什么显示‘重启后才更新’?脚本跑完是否就代表升级生效?”
本文以真实运行日志为例,拆解 OTA 触发、解包、分区写入、_alt 交换、Capsule 投递与验证步骤,建立清晰的心智模型。

2. 三套机制必须区分清楚:A/B、_alt、Capsule
2.1 Rootfs A/B(槽位更新模型)
-
A/B 的核心是:同一类关键分区存在 A 槽与 B 槽两套。
-
运行时通常处于 A 或 B 其中之一。
-
本地 OTA 的常规策略是:
- 当前运行在 A → 更新 B
- 当前运行在 B → 更新 A
-
OTA 完成写入后,需要通过 重启与槽位选择机制使系统切到新槽位启动。
2.2 _alt(同名分区的安全交换更新)
-
_alt 机制常用于 recovery / recovery-dtb 等关键但不一定属于 rootfs 槽位的分区。
-
典型成对存在:
X与X_alt。 -
更新策略不是“同时更新两边”,而是:
- 先把新镜像写到
X_alt - 校验成功后 swap 分区 name/GUID,让
X指向新内容 - 把旧侧擦空,作为下一轮
X_alt
- 先把新镜像写到
该机制用于提升“断电/中断时的可恢复性”,与 A/B 槽位是两个不同层级的可靠性手段。
2.3 UEFI Capsule(bootloader 的延迟执行更新)
-
Capsule 模式下,Linux 阶段并不逐个分区刷 bootloader。
-
Linux 只做两件事:
- 将
*.Cap文件投递到 ESP 的EFI/UpdateCapsule目录 - 写入 UEFI 变量(例如 OsIndications)触发下一次启动执行更新
- 将
-
真正的 bootloader 更新发生在 下一次 reboot 的早期阶段(UEFI 执行)。
3. 真实日志复盘:本次 OTA 实际更新了哪些对象
以下结论来自一次 nv_ota_start.sh 的完整输出(日志编号示例:/ota_log/ota_YYYYMMDD-HHMMSS.log)。
3.1 关键判定:当前槽位与目标更新槽位
日志中出现:
ROOTFS_AB_ENABLED=1→ rootfs A/B 已启用ROOTFS_CURRENT_SLOT=0→ 当前运行在 A 槽(slot 0)UPDATE_SLOT=B→ 本次选择更新 B 槽
结论:rootfs 与 A/B 相关分区将以“更新 B 槽”为主线。
3.2 Rootfs(B) 的实际写入目标
日志中出现:
nv_ota_rootfs_updater.sh -p /dev/mmcblk0p2 ... system.imgRootfs on non-current slot(B) is updated
结论:rootfs(B) 被写入到设备节点 /dev/mmcblk0p2。
说明:
p2是“本设备当前 GPT 排布的结果”。脚本通常依据分区名/槽位映射解析设备节点,而非硬编码固定p2。
3.3 Kernel / Kernel-DTB(A/B 槽位分区)
日志中出现:
Updating B_kernel partition done(写入boot.img并回读校验)Updating B_kernel-dtb partition done(写入 DTB 并回读校验)
结论:本次更新了 B 槽对应的 kernel 与 kernel-dtb。
3.4 recovery / recovery_alt(_alt 安全交换更新)
日志中出现:
- 先对
recovery_alt写入并校验 Start swapping partition name and guid.Set name recovery_alt to ... and recovery to ...Erase recovery_alt ... successfully.
结论:recovery 采用 _alt 机制完成“写入 → swap → 擦空旧侧”的安全更新。
该现象常被误解为“两个分区都被更新”。实际上更新语义是:
- 新镜像先落在 alt
- swap 后
recovery指向新镜像 - 旧侧被擦空并作为下一次 alt
3.5 recovery-dtb / recovery-dtb_alt(若目标已一致则跳过)
日志中出现:
The recovery-dtb is already updated. Skip update process.
结论:recovery-dtb 已经与目标镜像一致,本次跳过写入流程。
3.6 ESP 分区镜像(esp.img 缺失 → 跳过分区级刷新)
日志中出现:
.../esp.img ... is not foundSkip updating esp partition as no valid image is found
结论:未进行 esp 分区的“镜像级刷新”。
注意:即便
esp.img缺失,bootloader capsule 仍可能会使用 ESP 挂载点作为投递目录。
3.7 Bootloader(Capsule 投递与触发)
日志中出现:
trigger_uefi_capsule_update- 将
TEGRA_BL.Cap复制到 ESP 的EFI/UpdateCapsule - 写入 UEFI 变量
Bootloader on non-current slot(B) is to be updated once device is rebooted
结论:bootloader 更新被“触发并排队”,实际刷写发生在重启阶段。
4. 为什么会更新这些分区:从脚本结构解释“谁决定刷哪些”
本地 OTA 的“刷写清单”由两层逻辑共同决定:
update_control文件决定“是否更新 rootfs / bootloader”- rootfs 分支内的固定流程决定“更新哪些分区集合”(包含 A/B 与部分 misc/_alt 分区)
4.1 update_control:决定更新大类(rootfs / bootloader)
典型代码路径:
-
nv_ota_update_implement.sh调用get_update_control $OTA_WORK_DIR UPDATE_BOOTLOADER UPDATE_ROOTFS -
get_update_control()读取OTA_WORK_DIR/update_control的内容(例如包含bootloader、rootfs两行) -
读取到关键字后设置变量:
UPDATE_BOOTLOADER=1UPDATE_ROOTFS=1
当日志显示:
UPDATE_BOOTLOADER=1, UPDATE_ROOTFS=1
意味着两条分支都会执行。
4.2 Rootfs 分支:在 A/B 启用时固定更新非当前槽 + kernel + dtb
当检测到:
ROOTFS_AB_ENABLED=1
主流程会进入“rootfs A/B 更新路径”,其典型顺序为:
- 选择更新槽位(A → 更新 B,B → 更新 A)
- 更新 misc 分区(可能包含 recovery/recovery-dtb/esp 等)
- 更新 rootfs(非当前槽)
- 更新与槽位绑定的 kernel
- 更新与槽位绑定的 kernel-dtb
- 清理/复位槽位链路状态变量(例如 rootfs 状态变量)
4.3 Bootloader 分支:Capsule 投递与触发
当 UPDATE_BOOTLOADER=1,脚本通常不会在 Linux 里直接刷写一长串 bootloader 分区,而是:
- 挂载 ESP(脚本常见挂载点:
/opt/nvidia/esp或类似路径) - 复制
TEGRA_BL.Cap到EFI/UpdateCapsule - 写 UEFI 变量触发下次启动更新
这就是日志出现“once device is rebooted”的原因。
5. OTA_WORK_DIR 与 internal_device:它们与 payload 的关系
5.1 OTA_WORK_DIR(例如 /ota_work):解包后的执行现场
nv_ota_start.sh 的核心动作之一是将 payload 解包到 OTA_WORK_DIR。该目录通常包含:
- 解包后的
ota_package.tar(及其校验) - OTA 运行脚本(
nv_ota_*.sh、*.func等) - 控制文件(
update_control、备份清单等) internal_device/(核心镜像集合)- 若干运行时临时文件与备份文件
因此,OTA_WORK_DIR 不仅包含“要刷的镜像”,还包含“决定怎么刷”的策略与脚本。
5.2 internal_device:用于刷写的核心镜像集合
典型结构:
internal_device/system.img:rootfs 镜像internal_device/system.img.sha1sum:rootfs 校验internal_device/images-R36-ToT/:分区镜像集合(kernel/dtb/recovery 等)与映射索引信息
可将 internal_device/ 视为“payload 中用于设备刷写的核心组件集合”。
6. 为什么日志里看到具体的 /dev/mmcblk0p9 / p12 / p2?是否写死?
一般情况下并非写死,原因如下:
- recovery 类分区:脚本按分区 名称/label 查找对应设备节点,设备上具体落到
p9/p12取决于 GPT 布局 - rootfs(B):脚本按槽位映射选择 B 对应的 rootfs 分区,设备上具体落到
p2同样取决于 GPT 布局 - kernel/dtb:部分实现会根据索引(如
flash.idx或同类映射文件)以 offset 写入块设备,因此日志可能显示 offset 而非pN
排障时建议优先关注:
- 日志中的 分区名(recovery、B_kernel 等)
- 槽位变量(
ROOTFS_CURRENT_SLOT、UPDATE_SLOT)
而非过度依赖 pN 号在不同设备上的一致性。
7. 3 分钟验证清单:确认“写入完成”与“重启生效”
7.1 OTA 跑完但未重启:确认 B 槽内容已写入
- 查看槽位信息:
sudo nvbootctrl dump-slots-info
sudo nvbootctrl get-current-slot
- 挂载 B 槽 rootfs 分区检查版本文件(示例以日志中的
/dev/mmcblk0p2为准):
sudo mkdir -p /mnt/rootfsB
sudo mount /dev/mmcblk0p2 /mnt/rootfsB
cat /mnt/rootfsB/etc/nv_tegra_release 2>/dev/null || true
sudo umount /mnt/rootfsB
含义:即使系统仍在 A 槽运行,也能确认 B 槽 rootfs 真实被更新。
7.2 重启后:确认切槽与版本生效
sudo nvbootctrl dump-slots-info
sudo nvbootctrl get-current-slot
cat /etc/nv_tegra_release
uname -a
目标状态通常包括:
- current slot 指向新槽位(例如 B)
- 系统版本信息与新包一致
- 槽位状态正常(slot status/boot success 相关字段合理)
8. _alt 分区 vs 无 _alt 分区:差异总结
8.1 有 _alt:更强的更新可靠性(写 alt → 校验 → swap)
-
适用:recovery 这类关键分区
-
优点:
- 更新中断更容易恢复
- 可分阶段推进(日志常出现类似 S1/S4 的状态描述)
8.2 无 _alt:直接覆盖写(或按 A/B 槽位写入)
- 适用:kernel 等与槽位绑定的分区
- 优点:流程简单
- 风险:如果该分区本身不具备“同名备用副本”,中断时需依赖其它机制兜底(例如 A/B 槽位回滚、boot chain 校验等)
再次强调:
- _alt 是同一功能分区的安全更新副本机制
- A/B 是整套系统运行与更新的双槽机制
两者解决的问题不同。
9. 常见提示与误判点(基于日志场景)
9.1 “脚本跑完了,但系统看起来没升级”
常见原因:
- bootloader capsule 需要重启后执行
- 当前仍在旧槽位运行,尚未切换到更新后的槽位
建议按第 7 节完成“未重启写入验证”与“重启后生效验证”。
9.2 “esp.img not found” 是否等于失败?
不必直接判定为失败。该信息仅表示:
- 本次 OTA 包未提供 esp 分区镜像,因此脚本跳过 esp 的镜像级刷新
如果 capsule 投递正常,ESP 仍可能被用于 capsule 文件存放与触发。
9.3 “entry_table.ota_backup not found” 是否等于失败?
该信息更像是“备份文件缺失”的提示。是否影响升级,需要结合脚本是否在此处 exit 以及后续关键步骤是否继续推进来判断。若流程持续执行且写入校验通过,一般不是主致命点。
10. 本次升级的“分区更新清单(按模块归类)”
以下清单可直接用于项目文档/交付记录。
10.1 Rootfs A/B(目标:B 槽)
- rootfs(B):
/dev/mmcblk0p2(从日志得出) B_kernel:写入boot.img(回读校验通过)B_kernel-dtb:写入kernel_tegra...dtb(回读校验通过)
10.2 _alt 安全更新分区
recovery/recovery_alt:完成写入、swap 与擦空流程recovery-dtb/recovery-dtb_alt:目标镜像已一致,跳过写入
10.3 ESP / Capsule
esp.img:缺失 → 跳过 esp 镜像级更新TEGRA_BL.Cap:已投递到EFI/UpdateCapsule并设置触发变量 → 重启后执行
11. 附:如何把 OTA 包进一步“解剖成对照表”
为了形成可审计的“刷写清单”,可将以下信息整理成表格:
- 分区名(例如 B_kernel、recovery)
- 镜像文件名(例如 boot.img、recovery.img)
- 写入方式(分区设备节点 / offset 写入 / _alt 交换 / capsule 投递)
- 校验方式(sha1、回读比对、UEFI 变量/状态)
所需输入通常包括:
internal_device/images-R36-ToT/的文件清单(ls -l)- images 目录下用于映射的索引文件(例如
flash.idx或同类映射文件) - OTA 日志末尾关键段(包含写入与校验结果)
整理后即可得到一张“分区名 → 镜像 → 实际设备/offset → 校验结果”的对照表,用于发布记录或交付文档。
12. 复盘性总结(可作为博文结尾)
Jetson AGX Orin 的本地 OTA 日志需要从三套机制解读:
- A/B 决定 rootfs 与关键槽位分区只更新 非当前槽
- _alt 决定 recovery 类分区采用“写 alt → swap → 擦空旧侧”的安全更新
- Capsule 决定 bootloader 更新属于“Linux 投递触发,重启后 UEFI 执行”的延迟生效模式
当日志显示:
UPDATE_SLOT=BRootfs on non-current slot(B) is updatedUpdating B_kernel ... donetrigger_uefi_capsule_update+once device is rebooted
即可判定:
- B 槽 rootfs/kernel/dtb 已写入
- recovery 已按 _alt 完成安全更新
- bootloader 更新已触发并等待重启执行
验证步骤应分成两阶段:
- 重启前验证 B 槽确已写入
- 重启后验证切槽与 capsule 执行结果
📺 B站:博主个人介绍
📘 博主书籍-京东购买链接*:Yocto项目实战教程
📘 加博主微信,进技术交流群: jerrydev

被折叠的 条评论
为什么被折叠?



