27 届嵌入式 OTA 面试题汇总:分区划分、掉电恢复与 Bootloader 职责
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 什么是 OTA?和 IAP、ISP 有什么区别?
- 一次完整的嵌入式 OTA 升级流程怎么走?请按步骤说明。
- Bootloader 和 App 双分区一般怎么划分?起始地址和大小如何确定?
- 为什么 App 链接地址不是 0 地址时要重定向中断向量表?VTOR 什么时候设置?
- 升级标志、版本号、固件长度、CRC 这些元数据通常放在哪里?为什么不随便塞进 App 代码区?
- 上电后 Bootloader 如何根据元数据决定进入 App、等待升级还是进入恢复流程?
- OTA 传输协议怎么设计比较合理?如何做分包、序号、重传和结束确认?
- 固件写入 Flash 时,擦除和编程的最小单位分别是什么?跨页/跨扇区怎么处理?
- 如何校验升级包完整性?只用长度够不够?CRC、哈希分别适合什么场景?
- 升级写到一半突然掉电,系统重启后应该处于什么状态?如何保证不变砖?
- 单分区升级和 A/B 双备份升级各有什么优缺点?校招项目里更常见哪种?
- 升级成功后为什么要清升级标志再跳转?如果标志不清会有什么后果?
- OTA 过程中如何做版本管理?如何防止旧版本覆盖新版本,或重复升级同一包?
- Bootloader 里能否直接使用 App 的全局变量、中断服务和堆?为什么?
- 如果让你从 0 做一个最小可用 OTA,你会如何拆模块,首版先实现哪些能力?
《参考解析》
先准备一个「最小可讲述版本」,面试官是顺着项目往下追的。 简历上写了 OTA / IAP / 远程升级,面试官通常不会让你背定义,而是按固定路径往下钻:先让你讲整体流程(谁发起、谁接收、谁校验、谁跳转),再盯分区与地址(Boot、App、备份区、参数区各在哪),接着问可靠性(写到一半断电、CRC 错、版本回滚、重复升级),最后落到工程细节(协议怎么定、有没有断点续传、日志怎么留)。所以备考的重点是把一个真实项目串成一句话能背下来的链路:「双分区 + 升级标志 + 长度与 CRC 校验 + 校验通过后清标志再跳转」。每一环都要能就地展开成地址、数据结构或时序,讲不出细节的那一环就是被追着问的地方。
OTA、IAP、ISP 的区别要落在一句话的边界上。 OTA(Over-The-Air)强调升级包的传输通道是无线网络(4G、Wi-Fi、蓝牙),是「从哪来」的说法;IAP(In-Application Programming)强调在程序自己运行的控制下擦写 Flash 完成升级,是「谁来写」的说法,两者常常一起出现但不互为同义词;ISP(In-System Programming)是芯片在系统内的编程方式,通常指通过出厂自带的 BootROM 与串口、USB、SWD 等物理接口把程序烧进去,一般不依赖已有的应用程序。答完之后最好补一句实际形态:量产设备的 OTA 就是「云端下发 → 设备下载校验 → Bootloader 或 App 自己写入备份区 → 重启由 Boot 决定跳转」。
分区划分与中断向量表重定向是两道必考硬细节。 双分区的最小形态是 Bootloader 区、App 运行区、备份(下载)区、参数区四块;划分时先看 Flash 物理结构(扇区大小、能否单独擦除)和芯片的启动映射,再给 Boot 留出足够空间(协议栈、校验算法、日志、恢复逻辑都可能塞进去)。App 的链接脚本里 FLASH ORIGIN 是 App 区的起始地址,所以 App 运行后必须把中断向量表重定位过去:Cortex-M 上写 SCB->VTOR = APP_BASE,时机是在 App 启动的最早期、任何中断被使能之前完成。否则中断仍然按 Bootloader 的表去取入口地址,轻则跳飞到 Boot 的旧中断处理函数、重则直接 HardFault。这一题面试官往往接着问「谁来做这件事、能不能用 Boot 的重定向函数」,这里要明确:跳转前由 Boot 关中断、设置好栈指针与复位向量,进入 App 后由 App 自己尽早设置 VTOR,职责边界不能混。
升级元数据为什么必须单独放,是这组题里最能体现工程素养的一题。 升级标志、版本号、固件长度、CRC、目标分区这些信息要放在专门的参数区(或独立扇区、备份寄存器、带掉电保护的 EEPROM),不能塞进 App 代码区——因为元数据会被 Bootloader 在跳转前读取,而 Bootloader 无法也不应该去解析 App 的代码布局;更关键的是元数据要能被单独擦写和单独校验,写在 App 区里意味着每次升级元数据也跟着变,一旦升级失败连「上一次是什么版本」都拿不回来。工程上要讲三点:一是元数据要带自身的校验(CRC 或双份冗余),写坏了要有默认值与恢复路径;二是写入顺序要设计成「先写内容、后写标志」这类不可逆的顺序,任何一步掉电都能被识别为未完成;三是参数区要有磨损与寿命考虑,频繁写标志会耗尽擦写次数。
掉电一致性与不变砖:靠状态机加写入顺序,不靠运气。 设计目标是任何时刻断电,设备重启后要么进旧版本、要么进新版本,绝不进入「两边都不可用」的状态。实现要点:升级包先完整写入备份区,边写边算 CRC,全部写完并校验通过后才覆盖运行区(或者干脆不覆盖、用 A/B 切指针);覆盖与状态标记顺序固定为「擦除 → 写入 → 校验 → 更新元数据 → 置位升级成功标志 → 清下载标志 → 跳转」,每两步之间掉电,上电后 Boot 都能从元数据判断出处于哪个阶段并决定是继续、重试还是回滚;A/B 双备份天然满足这一点,代价是 Flash 容量翻倍,所以单分区方案更依赖谨慎的顺序设计加一份「永远不被覆盖」的 Bootloader。还可以补一句看门狗与超时:升级过程中要有下载超时、校验超时与最大重试次数,卡在某个阶段不能无限等,超时就回到可运行状态。
单分区与 A/B 的取舍,以及「清标志再跳转」的真实动机。 单分区省 Flash、结构简单,适合资源紧的 MCU,缺点是升级过程中掉电风险高、难以回滚;A/B 双备份可以随时回滚、可以在运行新版本的同时后台下载,代价是容量翻倍与切换逻辑更复杂,量产联网设备基本都选它,校招项目里做串口或网口 IAP 的小 Demo 则常见单分区加备份区。至于是不是「A/B」,面试官真正想听的是你怎么定义回滚判据——新版本启动失败(启动次数计数、自检不通过、心跳超时)就切回旧分区。最后一问「为什么升级成功后要清标志再跳转」的答案很干脆:标志是「有未完成任务」的信号,如果不清就跳转,下次上电 Boot 仍然认为要升级,会再次进入升级流程——表现为反复重启、反复自升级,甚至把已经正常的系统刷坏;正确顺序是校验通过、确认要跳转的那个分区可启动之后,再清标志(并同步更新版本号等元数据),保证重启后走的是正常启动路径。
「从零做一个最小可用 OTA」按模块拆,首版只做闭环。 模块拆法可以照链路走:通信与下载模块(协议、分包、序号、重传、断点续传与超时)、存储模块(Flash 擦写封装、分区管理、跨扇区缓冲)、校验模块(长度 + CRC32,需要防篡改再上哈希与签名)、元数据管理模块(标志、版本、长度、CRC 的读写与掉电保护)、状态机与执行模块(Boot 的判定逻辑、跳转与回滚)、日志与上报模块。首版建议只实现最小闭环:串口或网口升级、备份分区写入、长度与 CRC32 校验、升级标志置位、Boot 判定跳转、成功后清标志。第二版再补断点续传、A/B 回滚、版本防回退与签名校验、升级失败上报。这样回答能同时体现「知道完整面」和「知道先做哪一步」,比一上来就把所有特性堆上去更像做过真实项目。相关追问也要准备好:Bootloader 里不能直接用 App 的全局变量、中断服务和堆,因为两者的链接地址与向量表不同、内存布局也没被初始化,Boot 只能访问自己链接进来的符号;Boot 与 App 之间要传信息,只能通过约定好的固定地址结构体、共享内存区或备份寄存器,绝不能靠跨镜像的符号引用。