Jetson A/B 分区与回滚机制:官方视角的总结与实战记录(L4T 36.x / Orin / UEFI)


📺 B站博主个人介绍

📘 博主书籍-京东购买链接*:Yocto项目实战教程

📘 加博主微信,进技术交流群jerrydev


Jetson A/B 分区与回滚机制:官方视角的总结与实战记录(L4T 36.x / Orin / UEFI)

写作定位:总结 / 记录(像项目笔记一样可直接落地),重点把“分区—元数据—判定—回滚—验证闭环”讲清楚。

适用范围:JetPack 6(L4T 36.x),Orin(t234),UEFI(官方文档称 cpu-bootloader)。


0. 先把结论放在最前面(避免绕)

  1. Jetson 的 A/B 不是一个开关,而是两层机制:
  • Bootloader A/B:保护启动链固件分区,失败回退发生在 Linux 之前。
  • Rootfs A/B:保护根文件系统 APP/APP_b,失败计数常见为最多 3 次(由刷机参数决定),计数与切换在 cpu-bootloader 阶段完成。
  1. nvbootctrl用户态入口,可以查看/设置槽位;但“到底从哪个槽启动”由 bootloader 链路按同一套元数据执行。

  2. “最终使用的分区表”不是你看到的某个模板 XML,而是刷机时选中的 XML 经变量替换后生成的最终展开版(刷机过程中会生成最终 XML,写入 GPT 的分区名与大小以它为准)。

官方文档对应章节(建议你按这个顺序阅读):

  • Partition Configuration(分区配置 / XML 模板与最终展开)
  • Update and Redundancy(Bootloader A/B)
  • Root File System Redundancy(Rootfs A/B:APP/APP_b、3 次机制、verify 闭环)

在这里插入图片描述

1. 术语与对象:你要保护的到底是什么

1.1 Bootloader A/B(启动链固件冗余)

  • 对象:启动链关键分区(典型命名 A_cpu-bootloader/B_cpu-bootloader,以及其他 A_/B_ 配对分区集合,取决于你的分区表)。
  • 阶段:BootROM / MB1 / MB2 / UEFI(cpu-bootloader)这条链路内。
  • 目标:即使一套链路坏了,也能从另一套把系统拉起来。
  • 行为:槽位会维护一套状态字段(active/priority/tries/success/bootable 等);失败会导致当前槽被标记不可用并回退到另一槽。

1.2 Rootfs A/B(根文件系统冗余)

  • 对象:根文件系统分区 APPAPP_b

  • 阶段:主要在 cpu-bootloader 尝试启动 Linux 的流程中。

  • 目标:当前 rootfs 启动失败时自动切到备用 rootfs。

  • 关键机制

    • 最大重试次数常见默认 3(由 ROOTFS_RETRY_COUNT_MAX 配置,范围 0~3)。
    • retry count 存在 scratch register(跨 warm reset 保留、掉电清空)。
    • 成功标记闭环:系统启动成功后,Linux 侧服务会执行 nvbootctrl verify 来完成“成功确认/清理计数/更新状态”等动作。

1.3 重要提醒:两层机制不是“同一个计数”

  • Bootloader A/B:维护的是启动链槽位状态机字段。
  • Rootfs A/B:维护的是 rootfs 尝试次数(retry count)与当前 rootfs 槽位。

它们可能联动(尤其是 A↔B 配对设计),但不是同一套计数


2. 分区:先确认你是否真的启用了 Rootfs A/B

2.1 目标机上最权威的确认:看 GPT 分区标签

在目标机上执行:

lsblk -o NAME,SIZE,PARTLABEL,PARTUUID,MOUNTPOINTS

# (可选)更直观打印 GPT 表
sudo sgdisk -p /dev/nvme0n1 2>/dev/null | head -n 120 || true
sudo sgdisk -p /dev/mmcblk0 2>/dev/null | head -n 120 || true

你要得到一个非常明确的结论:

  • 是否存在 APP_b

    • :Rootfs A/B 启用 → 才存在“3 次失败回退”这套机制。
    • 没有:Rootfs A/B 未启用 → 只讨论 Bootloader A/B(以及你的内核/根分区本身是否冗余由你自己方案决定)。

2.2 主机侧:到底是哪份 XML 定义了分区

你在 Linux_for_Tegra/tools/kernel_flash/ 看到的例如:

  • flash_l4t_t234_nvme.xml
  • flash_l4t_t234_nvme_rootfs_ab.xml
  • flash_l4t_t234_nvme_rootfs_enc.xml
  • flash_l4t_t234_nvme_rootfs_ab_enc.xml

它们是模板。真正落地:

  • <board>.conf(内部存储)与 l4t_initrd_flash.sh -c(外部存储)共同决定。
  • 刷机时会把模板 XML 中的关键字替换成实际值,生成最终展开版 XML;GPT 按最终展开版写入。

实战记录建议:

  • 你每次量产/版本发布,都把“刷机命令 + 最终展开 XML + 目标机 GPT 分区列表”存档,这能极大降低后续排查成本。

3. 元数据:slot/tries/success 到底存在哪里

这一节只保留“对排查最有用”的结论。

3.1 Rootfs retry count:在 scratch register(易失)

  • scratch register 的特点:

    • warm reset 保留(所以连续失败可以计数)
    • 掉电清空(冷启动通常会重新初始化)
  • 所以你会看到典型现象:

    • 连续 warm reboot 会消耗 tries
    • 彻底断电再上电,tries 可能重置

3.2 Bootloader A/B 的槽位选择:冷启动与 warm 启动读取路径不同

  • 冷启动:BootROM 从非易失来源(例如 BR-BCT/相关结构)获取当前槽位。
  • warm 启动:BootROM 会使用 scratch register 缓存的槽位信息继续启动。

这一点决定了你在排查“为什么掉电后槽位又变了/为什么 warm reboot 一直回退”的现象时,必须把“冷启动 vs warm 启动”区分开。

3.3 Linux 侧闭环:nvbootctrl verify 与系统服务

Rootfs A/B 不是“bootloader 计数完就永远正确”,它需要 Linux 侧在启动成功后做确认/清理。官方流程中会有相关服务(常见为 nv-l4tbootloader-config.service)去调用 nvbootctrl verify

实战技巧:如果你开机后立刻 reboot,可能导致 verify 服务没跑完,从而出现“明明能进系统,但 tries 继续被消耗”的错觉。


4. 判定与回滚:官方逻辑用“时间线”理解最清晰

4.1 Bootloader A/B:更早阶段的失败回退

时间线(只保留关键点):

  1. BootROM 选择 boot slot(冷启动/温启动路径不同)。
  2. MB1/MB2/UEFI 按该 slot 加载启动链组件。
  3. 如果该 slot 的链路失败(不可加载/校验失败/不可启动等),状态机将其标记为不可用并回退到另一 slot。

你在用户态最容易看到的证据:

sudo nvbootctrl dump-slots-info

你要重点关注的是:

  • current / active slot
  • priority
  • tries_remaining
  • successful / bootable

4.2 Rootfs A/B:3 次机制发生在 cpu-bootloader 尝试启动 Linux 时

时间线:

  1. cpu-bootloader 根据当前 rootfs slot 组织启动参数。
  2. 尝试启动 Linux(加载 kernel/dtb/initrd,挂载 rootfs)。
  3. 若失败 → retry count 递减。
  4. 达到阈值(常见 3 次耗尽)→ 切到另一 rootfs slot 再试。
  5. 两边都失败 → 进入 recovery 路径(具体取决于配置)。
  6. 成功进入 Linux 后 → Linux 服务执行 verify,清理/确认状态,形成闭环。

你在用户态的证据:

sudo nvbootctrl -t rootfs dump-slots-info
systemctl status nv-l4tbootloader-config --no-pager || true

4.3 “触发 rootfs 回滚时,是只切 rootfs 还是整套 A/B 都切?”

官方概念上:rootfs fail-over 的动作是“切 rootfs slot”。

工程上常见观测:如果你的平台配置是 Bootloader A ↔ Rootfs A、Bootloader B ↔ Rootfs B 的配对关系,那么 rootfs 切换往往表现为“整套 A↔B 都跟着变”。

结论写法建议(更不容易被挑错):

  • 触发原因:rootfs 失败计数到阈值。
  • 切换对象:rootfs slot。
  • 外观表现:在配对配置下,bootloader slot 可能同步变化(以 nvbootctrl 两个视角的输出为准)。

5. 配置点:在哪里设置“3 次”以及如何验证

5.1 3 次阈值怎么设置

在刷机阶段设置(不是运行时改 /etc/*.conf):

  • ROOTFS_AB=1:启用 rootfs A/B
  • ROOTFS_RETRY_COUNT_MAX=3:设置最大重试次数(0~3)

你使用 flash.shl4t_initrd_flash.sh,本质都是在刷机流程里把这一组配置落进启动链路可读的状态。

5.2 验证:把“分区 + 两个视角 + cmdline”一次性对齐

# 分区是否存在 APP_b
lsblk -o NAME,SIZE,PARTLABEL,MOUNTPOINTS

# bootloader 视角
sudo nvbootctrl dump-slots-info

# rootfs 视角
sudo nvbootctrl -t rootfs dump-slots-info || true

# root= 到底指向哪个分区
cat /proc/cmdline | tr ' ' '
' | grep -E '^root='

你要在笔记里写清楚三行:

  • GPT:APP / APP_b 是否存在
  • bootloader current/active
  • rootfs current/active/tries

6. 代码与落地:给一套“判断成功”的可用实现

你要求“给出具体判断代码”,这里提供两类:

  • Linux 侧(推荐量产):健康检查 + nvbootctrl verify 闭环
  • Bootloader 侧(说明逻辑骨架):状态机伪代码(用于你理解/审查/自研时对齐)

6.1 Linux 侧:健康检查脚本(建议用于产品)

核心思想:不要把“能进 userspace”当成功;让关键服务 + 业务自检通过后再执行 verify。

/usr/local/sbin/ab-healthcheck.sh

#!/bin/bash
set -euo pipefail

LOG=/var/log/ab-healthcheck.log

log() {
  echo "[$(date -Iseconds)] $*" | tee -a "$LOG"
}

# 1) rootfs 必须 rw
if ! mount | grep -E ' on / .*\(rw,' >/dev/null; then
  log "rootfs is not rw => FAIL"
  exit 1
fi

# 2) 关键服务(按你的产品修改)
REQUIRED_SERVICES=(
  "network-online.target"
)

for s in "${REQUIRED_SERVICES[@]}"; do
  if ! systemctl is-active --quiet "$s"; then
    log "service $s not active => FAIL"
    exit 1
  fi
done

# 3) 业务自检(可选)
if command -v curl >/dev/null 2>&1; then
  if curl -fsS --max-time 2 http://127.0.0.1:18080/health >/dev/null 2>&1; then
    log "app health OK"
  else
    log "app health FAIL"
    exit 1
  fi
else
  log "curl not found => skip app endpoint check"
fi

# 4) 成功闭环:verify
if command -v nvbootctrl >/dev/null 2>&1; then
  log "nvbootctrl verify"
  nvbootctrl verify || true
  log "PASS"
  exit 0
fi

log "nvbootctrl not found => FAIL"
exit 1

对应 systemd:/etc/systemd/system/ab-healthcheck.service

[Unit]
Description=A/B healthcheck then verify
After=multi-user.target network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ab-healthcheck.sh

[Install]
WantedBy=multi-user.target

启用:

sudo chmod +x /usr/local/sbin/ab-healthcheck.sh
sudo systemctl daemon-reload
sudo systemctl enable --now ab-healthcheck.service

记录型建议:

  • 在量产镜像里把这个服务作为“业务启动完成后的最后一道门”。
  • 把日志落到 /var/log/ab-healthcheck.log,方便复盘为何某次被判失败。

6.2 Bootloader 侧:最小状态机伪代码(用于理解与对齐)

注意:以下为逻辑骨架,不是 NVIDIA 源码拷贝。你做 bootloader 内强制切换时,必须读写与 nvbootctrl 同一套元数据,否则必然状态不一致。

#### 6.2.1 槽位元数据模型(抽象)

```c
typedef struct {
    uint8_t priority;
    uint8_t tries_remaining;
    bool successful;
    bool bootable;
} Slot;

typedef struct {
    Slot slot[2];
    uint8_t active; // 0/1
} AbMeta;
6.2.2 选择槽位(抽象)
int pick_slot(AbMeta *m)
{
    // 1) active 不可启动则切换
    if (!m->slot[m->active].bootable) {
        m->active ^= 1;
    }

    // 2) priority 高者优先
    int a = 0, b = 1;
    int pick = (m->slot[a].priority >= m->slot[b].priority) ? a : b;

    if (!m->slot[pick].bootable)
        pick ^= 1;

    // 3) tries 用尽且未 successful => 标不可启动并回退
    if (!m->slot[pick].successful && m->slot[pick].tries_remaining == 0) {
        m->slot[pick].bootable = false;
        pick ^= 1;
    }

    return pick;
}
6.2.3 Rootfs retry(抽象:max=3)
typedef struct {
    uint8_t a_tries;   // 0..3
    uint8_t b_tries;   // 0..3
    uint8_t current;   // 0/1
    uint16_t magic;    // 0xFACE
} RootfsRetry;

RootfsRetry read_retry_from_scratch(void);
void write_retry_to_scratch(RootfsRetry r);

int rootfs_pick_with_retry(int current_slot, int max_retry)
{
    RootfsRetry r = read_retry_from_scratch();

    if (r.magic != 0xFACE) {
        r.magic = 0xFACE;
        r.a_tries = max_retry;
        r.b_tries = max_retry;
        r.current = current_slot;
        write_retry_to_scratch(r);
    }

    if (r.current == 0) {
        if (r.a_tries > 0) r.a_tries--;
        if (r.a_tries == 0) r.current = 1;
    } else {
        if (r.b_tries > 0) r.b_tries--;
        if (r.b_tries == 0) r.current = 0;
    }

    write_retry_to_scratch(r);
    return r.current;
}

你做 bootloader 内“强制切换”时,至少要保证:

  • 切换的是同一套 active/current
  • tries/priority/success/bootable 同步维护
  • Linux 启动成功后仍能通过 verify 闭环把状态收敛

7. 排查套路(记录式清单)

7.1 三步定位你遇到的是哪一层回滚

  1. 看分区:是否存在 APP_b

  2. 看两份输出

    • nvbootctrl dump-slots-info(bootloader)
    • nvbootctrl -t rootfs dump-slots-info(rootfs)
  3. 看服务闭环

    • systemctl status nv-l4tbootloader-config
    • journalctl -b -u nv-l4tbootloader-config

7.2 常见坑(按官方机制解释)

  • 开机立刻 reboot:verify 没完成 → tries 被误消耗。
  • 只改 active 不维护状态字段:看似切槽,实则下一轮被状态机回写覆盖。
  • 只看模板 XML 不看最终分区/GPT:以为启用 rootfs_ab,实际没 APP_b

8. 你可以直接用于项目评审的 Checklist

  • GPT 分区中存在 APP_b(若需要 rootfs A/B)。
  • nvbootctrl dump-slots-infonvbootctrl -t rootfs dump-slots-info 输出可解释且一致。
  • 能手动切 bootloader slot 并验证生效。
  • 能模拟 rootfs 启动失败并观察 tries 递减与切换。
  • nv-l4tbootloader-config.service 正常完成;业务健康检查后执行 verify。
  • 断电冷启动与 warm reboot 的行为差异已记录并可解释。

9. 结尾:建议你贴这 3 段输出,我可以按“官方模型”逐行解读

sudo nvbootctrl dump-slots-info
sudo nvbootctrl -t rootfs dump-slots-info
lsblk -o NAME,SIZE,PARTLABEL,MOUNTPOINTS

我会直接给你一个“结论表”:

  • 你当前启用了哪几层 A/B
  • 当前槽/下次槽分别是什么
  • tries/success 字段是否处于健康状态
  • 若要做产品化健康检查,应该把成功判定挂在哪个服务之后

📺 B站博主个人介绍


📘 *博主书籍-京东购买链接**:[Yocto项目实战教程](https://item.jd.com/15020438.html)

📘 **加博主微信,进技术交流群**:  **jerrydev**

---

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值