eMMC 内部架构与分区模型
从 Host 控制器到 NAND 阵列的完整拆解 —— 分区模型、命令队列、缓存与可靠性机制的一份系统性参考 | 编制日期 2026-10-14 | 版本号 Rev 1.0
eMMC 在原理图上是一颗 BGA 封装、在软件上是一个块设备名,但在器件内部它是一套完整的存储子系统:有自己的命令解析器、命令队列、缓存、FTL 映射、ECC 引擎、坏块管理与磨损均衡。主控发出一条读命令到数据回来,中间经过多少层转换、哪些层会缓存、哪些层会重试、哪些层会静默地换地址——这些决定了很多在现场被当成「驱动 bug」的现象的真实成因。
这份文档只讲器件内部这一层:长什么样、数据怎么流动、分区怎么切出来、命令队列与缓存怎么工作、可靠性机制怎么落地。不写电气连接与信号完整性、不写驱动移植与命令流程的代码细节、不写 Linux 驱动栈的分层实现。三份文档是并列的互补关系,本文是它们缺失的那一块地基。
结论: eMMC 不是「一块会走总线的大容量 Flash」,而是一颗自带存储子系统的器件—— 它把「LBA 到物理位置的映射、ECC 纠正、坏块替换、磨损均衡、写缓存与合并」全部封在硅片里, 所以主控侧看到的只是一次普通的块读写,而实际的写入次数、物理位置、耗时与掉电行为, 全都被器件内部的策略改写了一遍。所有架构层的现场问题,几乎都能追到这七件事中的一件。 本文所有数值均为量级性描述,具体参数一律以正式 datasheet / JESD84 为准。
读者路径
- 分区切错了、镜像放错区:直接看第 3 章(六类硬件区域与能力对照)与附录 B(区域能力速查)。
- 写入丢了、乱序了:看第 6 章(缓存与非对齐写入)与第 4 章(RPMB 的原子性语义),这两章覆盖了绝大多数「数据丢了」的真实成因。
- 性能不如预期:看第 5 章(CMD 队列的收益条件)与 5.8 节(无 CMDQ 时的替代路径),先分清是「没并发」还是「写放大」。
- 启动失败、启动源搞不清:看第 9 章(Boot 分区与内部 ROM)与 9.6 节(分层定位)。
- 要评估寿命与健康度:看第 7 章(ECC、坏块、磨损均衡与寿命预算分摊)。
- 做选型与能力评估:看第 10 章(版本演进与能力矩阵)+ 11.2 节(能力协商字段清单)+ 附录 C(排查手册)。
1. 为什么需要理解 eMMC 的内部架构
这一章不介绍具体机制,只先把一件事说清楚:eMMC 的「外部接口」与「内部实现」之间存在巨大的抽象落差,而这个落差正是绝大多数架构层困惑的来源。
1.1 外行看到的一颗「SD 卡」,里面到底有什么
在软件视角里,eMMC 就是块设备上的一个设备节点:打开它,读一个扇区,写一个扇区,出错就返回错误码。这个视角是有意简化的,它成立的前提是「你不需要知道数据实际落在哪」。一旦出了问题——写入丢失、性能不达标、寿命掉得比预期快——这个前提就塌了,你必须能看到器件里面去。
器件内部实际发生的事情,与「读一个扇区」这六个字之间隔着至少六层转换:
- 接口层把命令和数据在引脚上串行化,Host 与器件之间跑的是一套有严格时序的协议;
- 命令解析器把命令字翻译成内部事务,并判断当前状态下这条命令是否合法;
- 队列调度器决定这条事务排在缓存前面还是 NAND 前面,排在读队列还是写队列;
- 缓存层可能直接命中并返回,也可能先收下再择机合并后写入;
- FTL 映射层把逻辑块号翻译成物理页号,这是磨损均衡与坏块替换的落地点;
- NAND 阵列真正完成页的编程或读取,期间可能触发内部 ECC 纠错、坏块重映射、页迁移。
这六层里任何一层都可以「悄悄改变」外部观察到的行为:缓存让写入延迟变得不确定,FTL 让同一个 LBA 前后落到不同物理块,ECC 让数据在被读出之前就已经被改写过一次。
| 外部观察到的现象 | 器件内部真正发生的事 | 如果只看外部会怎么误判 |
|---|---|---|
| 写完立刻掉电,重启后数据丢了 | 写入可能还压在写缓存里,没落 NAND;也可能已经落 NAND 但映射表没落 | 误判为驱动没调用写,或误判为文件系统问题 |
| 同样的写操作,时快时慢 | 写缓存是否命中、是否触发回写读改写、是否需要等内部擦除,路径不同 | 误判为总线不稳定或主控性能不够 |
| 连续写同一个 LBA,寿命掉得飞快 | 磨损均衡会把长期热写的 LBA 逐步挪到不同物理块;均衡未介入的窗口内,同一 LBA 的反复写入集中在少数物理块上,或写放大把 512 字节变成整页甚至整块 | 误判为「只是多写了点数据」 |
| 换了一块料,行为就不一样 | 不同厂商的缓存大小、调度策略、ECC 强度、磨损均衡粒度都不同 | 误判为板级问题或软件版本问题 |
| 写到某个固定地址开始大面积报错 | 那个地址可能落在 Host 保护区、Backup 区,或对应物理块已经耗尽 | 误判为容量算错或地址对齐问题 |
| 裸读能读出内容,但重启后内容变了 | ECC 纠正会改写出错的比特,主机拿到的是「正确后的数据」,与阵列里原始存的不完全一致 | 误判为器件不稳定 |
1.2 三层视角:焊盘与引脚 / 数字接口与协议 / 内部的 Flash 阵列
理解 eMMC 最有效的办法,是把它拆成三个抽象层级。这三个层级的关注点完全不同:最外层是物理的,中间层是协议的,最里层是介质的。绝大多数架构层误解,都发生在跨层推理——拿最外层的直觉去推断最里层的行为。
| 视角 | 关心什么 | 典型问题 | 本文章节 |
|---|---|---|---|
| ① 焊盘与引脚 | 电源域有几路、时钟从哪来、数据线几根、上电顺序要求 | 这颗料能不能在我这块板上跑起来?VDDIO 与 VDD 有先后要求吗? | 第 8 章(内部电源管理与低功耗) |
| ② 数字接口与协议 | 命令怎么发、数据怎么切、状态怎么读、队列怎么管 | CMD18 之后为什么必须按时发 CMD12?CMDQ 到底解决了什么? | 第 4、5、11 章 |
| ③ 内部的 Flash 阵列 | LBA 怎么映射到物理位置、ECC 怎么纠、坏块怎么换、寿命怎么摊 | 为什么同容量不同料的寿命差这么多?RPMB 为什么比 User 区更可靠? | 第 3、6、7 章 |
排查问题时先定位你在哪一层,能省掉大量无效尝试:上不了电、识别不到器件在第 ① 层,查电源时序、电平、时钟与初始化命令序列;能识别但命令报错、数据不对在第 ② 层,查协议时序、命令前缀、块长度设置与队列状态;偶尔出错、性能波动、寿命异常在第 ③ 层,查写放大、缓存策略、磨损均衡与 ECC 行为。
最容易浪费时间的做法就是拿第 ③ 层的直觉去解释第 ② 层的现象——例如用「NAND 页要整页写」去解释「为什么块设备一定要按 512 字节对齐读」,这两件事其实处在完全不同的层。
1.3 架构知识在哪些具体问题上值钱
下面这张表是本文的价值清单:每一行都是一个真实会被问到的问题,以及它对应的架构层与本文位置。
| 现场问题 | 真正该问的是什么 | 对应架构层 | 本文位置 |
|---|---|---|---|
| 镜像烧不进 Boot 分区 | Boot 分区的容量是出厂固定的,容量算错就是烧不进;它不是普通分区,Host 侧只能按规范规定的窗口访问 | ③ 介质 / ② 协议 | 第 3.2、3.5、3.9 节 |
| 密钥存在 User 区,被读走了 | User 区没有任何访问控制,谁拿到卡谁就能读到全部 LBA;认证数据必须放 RPMB | ③ 介质 | 第 3.3、4 章 |
| 掉电后数据出现错乱或丢失 | 写缓存里的数据在掉电时是否已经落 NAND、映射表是否先于数据落盘,是两个独立的原子性问题 | ③ 介质 | 第 6.5 节 |
| 顺序写很慢但 CPU 占用很高 | 单任务串行模式下 Host 在空转等 NAND,CMDQ 才能把等待时间填满 | ② 协议 / ③ 调度 | 第 5 章 |
| 启用 CMDQ 后反而更慢 | 队列太浅、或任务粒度太细,任务切换开销超过收益 | ② 协议 | 第 5.6、5.7 节 |
| 日志区寿命远快于预期 | 写放大把随机小写变成整块改写,Boot 区被高频写会拖累整片寿命预算 | ③ 介质 | 第 6.3、7.5 节 |
| 整机静态功耗偏高 | 器件停在低功耗状态的条件没满足,或者内部电荷泵在高频访问后没能及时关闭 | ① 物理 / ③ 介质 | 第 8 章 |
| 设备上电后偶尔起不来 | 启动源指向了一个空 Boot 分区,或 Boot 分区切换状态与实际镜像位置不匹配 | ② 协议 / ③ 介质 | 第 9 章 |
1.4 全局架构总览图
把上面三个层级画进一张图,就是本文的总纲。后面每一章都只是在这张图的某一块上放大。
1.5 本文的主线:数据在器件内部怎么流动
全文围绕一个问题展开:一条命令从 Host 发出,到数据被交还,中间的每一层都做了什么决定。为了把这条链条讲完整,本文按下面这个顺序推进:
- 第 2 章先把器件内部剖开,看三大功能块与控制器的八类子模块,再走一遍一次读写的完整旅程。
- 第 3、4 章讲分区模型:六类硬件区域的边界、容量与能力差异,以及 RPMB 如何用认证语义实现防回滚。
- 第 5、6 章讲性能的两大引擎:命令队列负责「让等待重叠」,缓存与非对齐写入负责「让写入变少」。
- 第 7、8 章讲可靠性与功耗:ECC、坏块、磨损均衡如何在硅片里落地,以及器件如何进入低功耗状态。
- 第 9 章到第 12 章把前面所有机制收到工程侧:启动流程怎么用 Boot 分区、版本演进带来了哪些新能力、系统侧该怎么配、以及一份可直接照做的检查清单。
为了与同系列另外三份文档划清边界,本文明确不写电气连接与信号完整性、焊接与老化(属电路设计指南);不写驱动移植步骤、命令序列代码与 SDK 调用方式(属软件设计规范);不写块设备层、MMC 子系统、GPT 与文件系统挂载顺序(属 Linux 驱动开发指南)。
本文只写器件内部这一层。读完你应该能回答的是:「这个现象是器件内部哪一层干的、它为什么有权这么干、我能通过系统侧配置改变它吗」。
2. 硬件架构:Host、控制器与 NAND 阵列
这一章把器件从外面切开。先给出三大功能块的划分,再逐个拆解内部控制器的子模块,然后跟着一条命令走完从 Host 到 NAND 再回到 Host 的完整旅程。
2.1 eMMC 器件的三大功能块
eMMC 之所以能被当成一颗「存储芯片」而不是「一颗 Flash」,根本原因在于它内部有一整套把 NAND Flash 变得像块设备的东西。这套东西可以清晰地拆成三大功能块。
| 功能块 | 职责 | 如果它不存在会怎样 | 本文相关章节 |
|---|---|---|---|
| Host 接口 | 物理引脚、串并转换、协议时序、命令与数据的收发、状态与响应编码 | NAND 的裸接口无法被标准主机控制器驱动,只能靠专用时序发生器访问 | 第 8 章、第 11 章 |
| 内部控制器 | 命令解析、队列调度、缓存管理、ECC、FTL 映射、坏块与磨损均衡 | Host 只能看到裸 NAND 的物理特性,容量、寿命与可靠性都无法管理 | 第 5、6、7 章 |
| NAND 阵列 | 真正存放数据的介质,按 die / block / page 组织,支持多道并行 | 容量为零,且任何写磨损与坏块都需要 Host 自己处理 | 第 2、7 章 |
2.2 内部控制器由哪些子模块构成
内部控制器不是一个黑盒,它由八类子模块拼成。理解这八块的职责边界,比记住任何一条命令流程都有用——因为工程上遇到的现象,几乎总能落到其中一块。
| 子模块 | 核心职责 | 它做的关键决定 | 典型影响 |
|---|---|---|---|
| 接口引擎 | 串并转换、采样与时序、块长度与超时管理 | 用多少时间片完成一次事务、超时后如何恢复 | 总线占用率、超时行为 |
| 命令解析器 | 识别命令字、校验前置状态、拆解参数 | 这条命令当前是否合法、是否需要重试 | 非法命令的响应码、超时行为 |
| 队列调度器 | 把命令转成任务、分配队列、决定执行顺序 | 哪条任务先跑、读与写怎么交错 | 并发度、尾延迟、顺序保证 |
| 缓存控制器 | 管理读缓存与写缓存的分配、合并与回写 | 数据是留在缓存里还是落 NAND、怎么合并 | 写入延迟抖动、写放大、掉电风险 |
| ECC 引擎 | 页级校验、纠错、错误分类与上报 | 哪些错能纠、哪些要标记坏块 | 读出数据的正确性、坏块增长速度 |
| FTL 映射 | 维护逻辑块到物理位置的映射表 | 这个 LBA 现在对应哪个物理块 | 寿命分布、并发写入的表现 |
| 坏块管理器 | 出厂坏块登记、增长坏块替换、坏块表持久化 | 遇到不可用物理块时替换到哪一块 | 长期可用容量、是否需要重分区 |
| 磨损均衡模块 | 按块级或页级分散擦写次数 | 下一次写落到哪一块 | 整片寿命分摊、单点磨损速度 |
因为同一个外部现象,落到不同子模块上,规避动作完全相反。「写入偶发丢失」若落在缓存控制器,应显式刷写缓存、缩短驻留时间;若落在 FTL 映射,应保证映射持久化顺序;两者都不解决才去怀疑 Host 侧。「性能不如预期」若落在队列调度器,要增加并发;若落在磨损均衡,要减少随机写、增大批量;若落在 ECC 引擎,说明读错误过多导致额外重读。
不做这个拆分,就会出现「明明换了料却没变化」「明明加了并发却更慢」这类无法收敛的排查过程。
2.3 NAND 阵列与数据通路:从 LBA 到页、块、物理位置
NAND 阵列的组织是分层的,从大到小依次是 die(裸片)→ block(块)→ page(页)。这三个层次各自对应一个操作粒度,且操作粒度与数据粒度并不一致——这是写放大与所有非对齐写入问题的物理根源。
- page:读操作的最小单位。读一个 page 会把整页的数据一次性搬到片上缓存,Host 侧只能取其中一段。
- block:擦除的最小单位。擦除一个 block 会把其中所有 page 全部恢复为初始态。
- die:独立的数据分区。同一时刻可以对不同 die 并行发起操作,这也是吞吐的主要来源。
关键的不对称在于:读以 page 为单位、写在 page 为单位、擦除以 block 为单位,而 Host 的访问单位是逻辑块。当一个逻辑块不能整除地映射到一个 page,或者需要改写的部分不足一个 page,主控就必须把动作放大到整页甚至整块。
| 层次 / 概念 | 定义 | 允许的操作 | 不允许的操作 | 工程含义 |
|---|---|---|---|---|
| LBA(逻辑块) | Host 侧看到的地址空间,以固定逻辑块大小为单位 | 按块读、按块写 | 不能按字节或任意长度写 | 块大小由能力协商确定,是所有对齐计算的基础 |
| page(页) | 读与写的基本单位,固定大小 | 读整页、写整页 | 不能只改页内的一部分而不重写整页 | 非对齐写必然触发回写读改写,见第 6.3 节 |
| block(块) | 擦除的基本单位,包含固定数量的 page | 整块擦除 | 不能擦除块内单个 page | 写放大与寿命消耗以块为单位计量 |
| die(裸片) | 独立的数据分区,多 die 可并行 | 跨 die 并行命令 | 同一 die 内的操作存在时序约束 | 并行度决定顺序吞吐的上限 |
| FTL 映射项 | 一个 LBA 当前指向的物理位置 | 改指向(不改数据即完成覆盖写) | 不能跨越坏块策略约束 | 覆盖写不等于原地写,这是寿命分散的关键 |
Host 侧协商出来的逻辑块大小(例如 512 字节或 4 KB)和器件内部的 NAND 页大小是两个完全独立的量,它们之间没有必须整除的关系。实践后果是:「按块访问」不等于「按块写入 NAND」——小块频繁写入会在器件内部被聚合成整页甚至整块写,「我一个月才写几十 MB,怎么寿命掉这么快」这类问题多半出在这里。
2.4 内部架构剖面图
把第 2.1 与 2.2 节的内容叠成一张剖面图,就是器件内部的真实形态。注意右侧的职责标注——这是排查问题时最常回查的一张图。
2.5 数据一次读写的完整旅程
现在把前面的概念串成一条完整链路。下面是一次读操作和一次写操作的完整路径,每一步都标注了它属于哪一层,这样以后看到现象就能立刻定位。
| 步骤 | 读操作(CMD17 / CMD18) | 写操作(CMD24 / CMD25) | 所属层 |
|---|---|---|---|
| 1 | Host 把命令字与参数放到命令通道上,器件按位采样 | 同左,命令指向目标块地址 | 接口层 |
| 2 | 命令解析器识别命令、检查前置状态(是否已选中、块长度是否设定) | 同左,另外检查写保护与目标区域是否合法 | 命令解析 |
| 3 | 任务进入读队列,等待调度器分配总线与时序片 | 任务进入写队列,可能先被写缓存吸收 | 队列调度 |
| 4 | FTL 查表得到物理页号 | FTL 查表得到目标物理页号;若命中已分配页则直接改写,若需新页则从空闲池取 | FTL |
| 5 | NAND 页读出,数据进入器件内部读缓存 | 写缓存暂存数据;若缓存不足则触发回写或合并 | 缓存 |
| 6 | ECC 引擎对整页做校验并纠正 | ECC 引擎在落盘时生成并写入校验信息 | ECC |
| 7 | 如有不可纠错错误,标记坏块并由磨损均衡改映射 | 如有坏块,改由备用块承接 | 可靠性 |
| 8 | 数据经数据通道返回 Host,状态寄存器置位 | 数据落 NAND 后更新映射与状态 | 接口层 |
| 9 | Host 轮询到完成,读取状态确认无错 | Host 轮询到完成;掉电风险点在第 5 到第 8 步之间 | 接口层 |
第 6 步 ECC 纠错会改写数据——Host 拿到的数据与 NAND 阵列里物理存放的位流并不完全相同,器件在读出路径上做了修正。两个后果:用裸读手段「验证数据是否与写入时一致」时,出现过软错误之后可能发现不一致,但这是正常的纠正结果而非器件缺陷;ECC 强度是设计取舍,纠错能力越强所需额外存储位越多、容量开销越大,读错误率超过纠错能力时才表现为「读不出来」而不是「读错了」。
2.6 器件内部的数据流总览
把一次读写拆成「谁在什么时候把活干完」,就能看清哪些阶段可以被重叠、哪些阶段必须串行。这张图也是第 5 章与第 6 章的铺垫。
2.7 厂商实现差异
标准定义的是外部可观察的行为,不定义内部怎么做。因此同容量、同版本的两颗 eMMC,在缓存大小、调度策略、ECC 强度、映射粒度上都可以差很多。这解释了为什么「换料就出问题」经常发生。
| 实现维度 | 差异的可能范围 | 对系统侧的表现 | 验证方式 |
|---|---|---|---|
| 内部缓存大小 | 从很小到较大,可能只读缓存与读写缓存分开 | 突发写入的抖动幅度不同,写放大倍率不同 | 顺序写与小随机写混合压测,对比延迟分布 |
| 写缓存策略 | 是否启用、合并窗口多大、什么条件下回写 | 掉电时可能丢多少数据窗口不同 | 写入过程中随机断电,检查最后一次提交的数据是否存活 |
| ECC 强度与算法 | 纠正能力与容量开销的取舍不同 | 读错误率不同、重读次数不同、可选终止时机不同 | 长时间读压测,统计可纠正错误上升趋势 |
| FTL 映射粒度 | 按逻辑块映射或按更细粒度映射 | 同容量下寿命分布差异明显 | 随机写压测后读取健康度信息对照 |
| 磨损均衡粒度 | 静态、动态或二者结合,块级或页级 | 频繁改写的小区域是否成为磨损热点 | 高频改写同一逻辑块,观察整体寿命 |
| 坏块策略 | 备用块数量、增长坏块替换阈值 | 长期使用后可用容量是否下降 | 老化测试中定期读取坏块相关信息 |
| 队列与并发实现 | 支持的队列数、队列深度、任务完成顺序规则 | 并发收益曲线不同、顺序保证不同 | 多队列并发压测并校验数据一致性 |
因为上述差异,换料不是「同容量可直接替换」。方案层面要把架构相关的行为做成可探测而不是可假设——启动时把能力协商字段完整取出来并记录存档,不要在代码里硬编码任何来自某颗料的经验值。验证层面要对每颗新料至少跑三类测试:混合读写压测(看延迟分布与写放大)、随机断电测试(看缓存与映射的原子性)、高频小写老化(看寿命分摊是否合理)。
更详细的可观测性字段见第 7.7 节,能力协商字段清单见第 11.2 节。
3. 分区模型:六类硬件区域
eMMC 的分区不是操作系统画的,而是器件出厂时就在硅片里切好的。这一章把六类硬件区域的边界、容量来源与能力差异讲清楚,并说明它们与 GPT 的分工关系。
3.1 为什么要有分区:掉电安全与多用途共享
如果 eMMC 只是一个大容量可写块设备,那么所有内容——启动镜像、密钥、日志、用户数据——就会混在同一片地址空间里,任何一次误写或掉电都可能同时毁掉它们。硬件分区模型的本质是用物理隔离换取语义隔离:让某些区域天生具备不同的能力,让另一些区域从物理上就不可能被普通写入碰到。
三条设计动机:掉电安全(启动镜像必须在掉电后仍完整可执行,需要一块写入策略更保守的区域)、认证性(密钥不能被任何人读到,需要一块伪造会被拒绝的区域)、多用途共享(同一颗器件要同时服务启动、认证、用户数据、备用,地址空间上必须分开)。
需要特别澄清的一点:硬件分区与操作系统的分区表是两套机制。硬件分区在器件出厂时就固定了;操作系统分区表(如 GPT)是写在某个区域里的数据结构,必须依附于硬件分区才能生效。这个分层关系是理解第 3.8 节的前提。
3.2 Boot1 / Boot2:上电即执行区
Boot1 与 Boot2 是器件内部预留的启动区,容量在出厂时固定。它们存在的理由很直接:系统上电后,主控还没有跑起来,此时唯一可用的存储就是这两块区域。主控的启动 ROM 只需要按固定规则从这里取数据,就能把第一段代码搬到 SRAM 或内部运行区执行。
Boot 分区有三个常被忽略但很重要的架构属性:
- 容量固定且很小:这是它作为「上电即用」区域的前提,也是最常见的现场错误来源(详见第 3.9 节)。
- 访问方式受限:Host 侧不是通过普通 LBA 访问它,而是按规范规定的分区号与固定偏移访问。
- 切换是双份的:Boot1 与 Boot2 成对存在,用于「一份在写、另一份保持有效」的更新模式;启动源指向哪一份由配置决定,不是 Host 临时决定。
| 维度 | Boot1 / Boot2 的架构属性 | 对设计的含义 |
|---|---|---|
| 容量 | 出厂固定,与具体型号绑定,不占用 User 区容量 | 镜像必须按容量裁剪,不能按 User 区容量规划 |
| 寻址方式 | Host 侧通过分区号加固定偏移访问,不是普通 LBA 空间 | 烧写工具要按分区号下发,而不是按地址 |
| 双份结构 | 成对存在,用于更新时一份在写一份保持有效 | 更新流程必须处理「更新中掉电」的中间态 |
| 启动源选择 | 由器件内配置决定指向哪一份或 User 区 | 切换要显式下发命令,不能靠写入位置隐式决定 |
| 内部 ROM 支持 | 规范定义了从 Boot 区启动的固定流程,ROM 负责搬运 | 第一级引导不必由主控代码实现 |
| 可靠性语义 | 写入量小、可校验,主要承担可引导责任 | 不适合放日志、参数或任何高频写数据 |
| 是否放文件系统 | 不放 | 镜像必须是裸的、位置固定的连续数据 |
3.3 RPMB:一写入一读取的认证区
RPMB(Replay Protected Memory Block,防重放保护存储区)是本文里最需要被正确理解的一块。它的核心不是「一块特殊存储」,而是一套带密钥认证的原子读写语义:写入必须携带正确的认证标签,读取返回的数据同样带标签,因此任何一方都能验证数据没有被仿造或重放。
三句话说清它的定位:
- 认证性:帧内携带由密钥派生的认证标签,主机与器件各自持有一份密钥,只有知道密钥的一方才能产生合法帧。
- 原子性:一次写入要么完整生效、要么完全不生效,不会出现「数据写了一半」的中间态。
- 防重放:每个成功写入都会递增一个写计数器;写入请求必须携带与器件当前值相等的写计数器,不等(无论大小)即拒绝,从而防重放。
| 维度 | RPMB 的架构属性 | 典型用途 | 误用后果 |
|---|---|---|---|
| 访问控制 | 无认证的读写一律被拒绝,只有合法帧才生效 | 密钥材料、授权票据、一次性凭证 | 把密钥放 User 区,等于公开存储 |
| 原子性 | 帧级原子,写入要么全成功要么不生效 | 需要保证「要么更新要么不变」的状态 | 把关键状态放在无原子保证的普通区 |
| 防重放 | 写计数器单调递增,落后或重复的计数被拒绝 | 固件版本、授权次数、事件序号 | 版本可被回滚,安全机制形同虚设 |
| 容量 | 出厂固定,版本不同大小不同,通常远小于 User 区 | 只放小体积、高价值的状态数据 | 拿它当大容量数据区用,根本装不下 |
| 性能 | 每次访问都要计算认证标签,开销大于普通读 | 低频关键数据 | 拿它存高频日志,性能无法接受 |
| 密钥位置 | 密钥本身存放在器件内的一次性可编程区 | 由一次性可编程机制锁定,不可改写 | 密钥存到外部 Flash,认证模型立刻失去意义 |
3.4 User 区:容量主体与 GP 分区
User 区是容量主体,标称容量基本都指它。它在架构上有一个关键特点:被切成若干通用分区(GP 分区),每个 GP 分区在主机侧表现为一个独立的逻辑块设备。GP 分区本身不带任何特殊能力,它的存在只是为了在一块物理器件上划出多个互相独立的空间。
需要注意的是,GP 分区与操作系统分区表是两回事:GP 分区是硬件层的切分(由器件配置决定,主机通过分区号选择),而 GPT / MBR 是软件层的切分(写在某个区域里,操作系统读它来挂载文件系统)。同一个 GP 分区内部可以再放一整张 GPT。
3.5 硬件分区地址映射与容量关系
下面这张图与接下来的对照表是本章最重要的一张图。关于容量只有一条必须记住的规则:Boot 与 RPMB 的容量来自器件出厂配置,不从 User 区里扣;而标称容量指的是 User 区的容量。把两者混在一起算,是分区规划阶段最常见也最难在早期发现的错误。
3.6 Backup 区与保留区
Backup 区不是给用户放数据的地方,它是器件给自己的:坏块表、映射信息、磨损均衡的统计状态等内部元数据存放于此,Host 侧不能把它当普通 LBA 访问。保留区则是厂商内部预留,既不可访问也不计入标称容量。
Backup 区的存在解释了一个常见现象:eMMC 的标称容量并不等于地址空间末端。地址空间的最高若干块往往落在保留区,越界访问得到的是错误而不是数据。如果系统侧按「容量 × 块大小」直接算地址上界,尾部可能撞进保留区。
3.7 三种区域的能力对照表
把 Boot 区、RPMB、User 区(含 GP 分区)放在一起对照,五个维度上的差异一目了然。这张表建议直接抄进设计文档作为分区选型的判据。
| 能力维度 | Boot 区(Boot1 / Boot2) | RPMB | User 区(含 GP 分区) |
|---|---|---|---|
| 可读性 | 可读,内容通常是裸镜像 | 可读,但只有合法认证帧能被接受 | 完全可读,无任何保护 |
| 可写入性 | 可写,但 Host 侧写入受限 | 可写,必须携带合法认证标签与正确计数 | 完全可写,无限制 |
| 认证性 | 无认证语义,靠位置约定 | 有,帧级认证,防仿冒 | 无,任何持有卡的人都能读到全部内容 |
| 原子性 | 取决于写入策略,可校验 | 帧级原子,保证要么全成功要么不生效 | 仅受写缓存与掉电检测影响,无强保证 |
| 防回滚 | 无 | 有,写计数器单调递增 | 无 |
| 容量 | 出厂固定,很小 | 出厂固定,很小 | 标称容量的全部,不含 Boot 与 RPMB |
| 寻址方式 | 分区号加固定偏移 | 分区号加固定偏移 | 普通 LBA 地址空间 |
| 典型用途 | 启动镜像引导代码 | 密钥、授权状态、版本计数 | 系统镜像、用户数据、日志、参数 |
| 是否适合放文件系统 | 否 | 否 | 是,这是它唯一适合做的事 |
| 写入频率预期 | 极低,只在升级时写 | 低,只在状态变化时写 | 高,可能持续高频写入 |
可读性与可写入性几乎所有人都懂,但下面两条经常在评审时被跳过:认证性是分区属性而非软件属性——用户区的任何 LBA 都能被任何持有卡的人读出来,加密只能靠数据本身,不能靠分区来加密;防回滚必须由器件保证——如果版本号或计数存放在用户区,攻击者或误操作可以写回一个更旧的合法镜像,防回滚立刻失效,其载体必须在 RPMB 这类带计数的区域里。
3.8 硬件分区与 GPT 的分工
硬件分区与软件分区表是上下两层,各管一件事。搞混这一层的常见后果是「分区表写对了但系统起不来」,或者反过来。
- 硬件分区:由器件出厂配置决定,划出 Boot、RPMB、GP 分区等区域。Host 侧通过分区号选择,写入这个动作发生在烧录阶段。
- GPT:是一张写在某个区域里的分区表数据结构,描述这块区域内部怎么再分。它是一个普通文件,位于 GP 分区或 User 区的开头,操作系统读取它来建立分区映射。
关键结论:Boot 分区里不放 GPT。原因很直接——GPT 的作用是描述「一个可被操作系统读取的块设备内部怎么分」,而 Boot 分区不是操作系统块设备,它是主控 ROM 直接读取的裸镜像区;把 GPT 写进去只会让 ROM 无法解析。
| 对比项 | 硬件分区 | GPT(软件分区表) |
|---|---|---|
| 由谁决定 | 器件出厂配置 | 你自己写进去的 |
| 什么时候确定 | 烧录阶段就固定 | 烧录之后可随时重建 |
| 作用范围 | 划出 Boot / RPMB / GP 等物理隔离区域 | 在某个块设备内部再划分逻辑分区 |
| 谁解析 | 主控的启动 ROM 与 Host 驱动按规范解析 | 操作系统分区解析层解析 |
| 能不能放文件系统 | Boot 与 RPMB 不能;GP 与 User 可以 | GPT 本身不是文件系统,它描述文件系统所在分区 |
| 写错的后果 | 启动失败或认证失败,现象明显 | 分区丢失或挂载错位,可能被误判为文件系统问题 |
| 是否需要备份 | 一般不备份(出厂固定) | 需要备份,重要设备建议留存原始分区表镜像 |
| 两者的关系 | GPT 必须依附在某个可被操作系统识别的区域里 | 通常写在第一个 GP 分区或 User 区开头 |
3.9 分区选择的常见错误
下面五条是分区规划阶段的高频错误,每一条都会在后期以很难排查的形式暴露出来。提前对照检查一遍,比事后追查便宜得多。
| 错误做法 | 为什么错 | 现场表现 | 正确做法 |
|---|---|---|---|
| 把启动镜像放在 User 区 | 主控 ROM 的启动源默认指向 Boot 分区;User 区里的东西不会被 ROM 自动取走 | 上电后卡在启动 ROM 阶段,日志里看不到任何应用层输出 | 启动镜像放 Boot 区;User 区只放主控 ROM 能定位到的第二级引导 |
| 把密钥材料放在 User 区 | User 区完全可读,无任何认证或保护 | 物理接触卡即可读出密钥,密钥体系形同虚设 | 密钥材料放 RPMB;User 区只放不可逆派生出的结果或引用句柄 |
| Boot 分区容量算错 | Boot 容量出厂固定,且两份都要放镜像 | 烧录工具报空间不足,或者烧进去了但 ROM 校验不过 | 先从数据手册确认 Boot 容量与启动源配置,再裁剪镜像 |
| 两份 Boot 区被同时擦写 | Boot1 与 Boot2 的设计前提是一份在写、另一份保持有效 | 更新中掉电后两份都不可引导,设备变砖 | 严格按「先写空闲份、校验通过后再切换启动源」的顺序更新 |
| 把 Backup 区当可用容量 | Backup 区是器件内部元数据空间,不对外承诺 | 尾部读写返回错误,文件系统末尾出现不可用空间 | 可用容量一律按数据手册给出的用户可用容量计算,不自行推断 |
4. RPMB 的认证模型与防回滚
这一章把 RPMB 拆到机制层面。核心命题只有一句:RPMB 的价值不在存储,在于它用器件内部的一次性密钥和单调递增的计数器,把「数据完整性」与「不可回滚」两件事从软件层的自觉变成了硬件层的强制。
4.1 RPMB 要解决的问题:回滚与仿冒
先说清它防的是什么。没有 RPMB 时,主机的安全状态存在普通区域里,会面临两类攻击:
- 回滚(replay):攻击者或误操作把状态数据恢复成一个更早的合法版本。例如把「已授权次数」改回未使用的状态,或把固件版本号改回旧版本,从而绕过「必须升级才能使用」的限制。
- 仿冒(forgery):直接构造一份看起来合法的状态数据并写入。在没有认证的区域里,这在技术上就是把几个字节写进去而已。
RPMB 用两个机制分别对付这两类问题:帧级认证标签对付仿冒,单调递增的写计数器对付回滚。缺了任何一个,模型就不完整——只有认证没有计数,就是防住了仿冒但防不住回滚;只有计数没有认证,攻击者可以自己构造一个计数更大的合法帧。
4.2 密钥的派生与存储位置
RPMB 的认证标签能防仿冒,前提是只有可信一方知道密钥。密钥的派生与存放本身就是一个架构问题,而不是一个密码学细节。
- 存储位置:密钥存放在器件内部的一次性可编程区域。这块区域在出厂后可以被写入一次,之后不可更改——这是模型的信任根。如果密钥存在外部 Flash 里,攻击者读到 Flash 就能生成合法帧,整个认证模型立刻失去意义。
- 派生方向:一般由主控在生产阶段生成一次性密钥,写入器件的一次性区;运行期器件与主控各自持有同一份密钥,任何一侧泄露都会让认证失效。
- 一次性可编程的含义:不可更改意味着无法轮换密钥,也无法擦除重来。因此密钥的烧写本身必须被当作一次性不可逆操作来管理,写错就没有补救路径。
工程上最容易被忽略的一点:密钥的烧写要在产线流程里明确留痕。一旦批量烧写后没有记录哪颗器件用了哪把密钥,售后换料时就无法复现,RCP 认证相关的功能会集体失效。
4.3 认证写入与认证读取的流程差异
认证写与认证读都带认证标签,但字段构成与副作用不同。下表列出关键差异,这些差异直接决定了驱动层的实现必须区别对待读写两条路径。
| 对比项 | 认证写入 | 认证读取 | 对驱动层的含义 |
|---|---|---|---|
| 是否携带认证标签 | 携带 | 携带 | 两条路径都要实现标签计算,不能只做写 |
| 是否需要写计数器 | 需要,且必须等于当前值 | 不需要 | 写前必须先读回当前计数,计数要由驱动维护 |
| 是否修改器件状态 | 修改:写入数据并递增计数 | 不修改 | 读操作不能被当作幂等无副作用的探测 |
| 失败后的状态 | 完全不变,计数不变 | 不变 | 写失败后可直接重试,不需要回滚动作 |
| 副作用的可观察性 | 计数器会前进 | 无副作用 | 掉电后计数是否前进决定了重试是否安全 |
| 性能特征 | 更慢:写入 NAND 且要算标签 | 较慢:全量数据都要算标签 | 不适合放在高频路径上 |
| 典型用途 | 更新状态、递增序号、写入凭证 | 读取状态、校验数据未被篡改 | 关键状态读取要走认证读而不是普通读 |
因为写计数器单调递增,一次成功的写入会让下次重试的计数失效。实际项目中并不罕见这个序列:写入请求发出 → 器件已完成写入并递增计数 → 但响应在总线上丢失或被主控判为超时 → 驱动认为失败,用同一个计数再次发起写入 → 器件以「计数已在更早处用过」拒绝。
此时把驱动当成普通错误继续重试会陷入永久失败;强行改用更大的计数去「试出来」则可能覆盖掉状态。正确做法是:任何写失败之后,必须先做一次认证读取取回当前计数器,再决定下一步。这条「失败后先读回」的纪律,是 RPMB 实现里最重要的一条。
4.4 计数与回滚保护:单调递增语义
写计数器是 RPMB 模型的支柱。它的语义非常朴素但非常严格:任何一次成功的认证写入,都必须让计数单调增加;写入请求必须携带与器件当前值相等的写计数器,不等(无论大小)即拒绝,从而防重放。
这条规则带来三个直接后果:
- 状态不可回退:攻击者无法把「已用 99 次」改回「已用 0 次」,因为他构造不出计数更大且通过认证的合法帧。
- 顺序即保护:计数值本身隐含了顺序信息,主机不需要额外的序号字段就能判断状态是否被倒退。
- 掉电后不会自愈:如果掉电发生在写入过程中,要么整帧生效且计数前进,要么完全不生效且计数不变——不会出现「数据写了一半、计数却已前进」的错位。
反过来也要理解它的成本:计数器空间是有限的,计数每次写入都前进,因此 RPMB 的总写入次数有上限。把高频事件持续写进 RPMB 是设计错误——它既会撞上计数空间,也会带来不可接受的开销。RPMB 应该只存「低频但高价值」的状态。
4.5 RPMB 帧结构与响应码流程
下面这张图把认证写入的帧字段与六个步骤画在一起,同时给出三类典型错误响应的成因与对策。它是实现与排查 RPMB 时最常回查的一张图。
4.6 常见失败模式与排查路径
RPMB 的失败往往表现为一句含糊的错误码。排查的关键是先判断失败发生在哪一层:是认证没过、计数不对、还是空间与帧长问题。这三类失败的处理方式完全不同,混在一起试会浪费时间。
| 失败模式 | 根因 | 典型触发场景 | 排查路径 | 规避动作 |
|---|---|---|---|---|
| 认证始终不通过 | 密钥不匹配或派生不一致 | 换料后密钥未同步写入;密钥烧写流程缺记录 | 确认器件内密钥来源与主控侧是否同一份派生结果 | 在产线记录每颗器件的密钥烧写结果 |
| 计数不同步 | 主控维护的计数落后于器件 | 写失败后未读回计数就重试;掉电后状态未恢复 | 写失败后强制读回当前计数并比对 | 实现「失败先读回」纪律,不盲目重试 |
| 响应超时 | 总线上有噪声或信号完整性不足 | 布线过长、缺少串联电阻、时钟质量差 | 先在电气层面排查,不要在协议层面硬改 | 先解决电气问题,必要时降低总线速率验证 |
| 空间不足 | 单帧数据超过帧容量或区域写满 | 想拿 RPMB 存大对象 | 核对帧容量上限与区域剩余量 | 只存小体积高价值数据,大对象放别处 |
| 返回计数始终为初始值 | 写入从未成功过 | 认证就没通过;或写入到了别的分区号 | 确认分区号选择与启动源配置 | 把分区号与用途做成显式配置项 |
| 换料后功能集体失效 | 密钥缺失或不一致 | 维修换料时未重新烧密钥 | 核对换料流程是否包含密钥烧写步骤 | 把密钥烧写写进换料作业标准 |
遇到任何 RPMB 相关问题,按这个顺序做三件事基本能覆盖九成场景:先做一次认证读取——能读出来说明认证链路与密钥没问题,问题在写入路径或计数,读不出来说明是认证或分区的问题;再比对读回的计数与本地计数——不一致说明有过「成功但主控不知道」的写入,计数必须以读回为准;最后才看电气与时序——响应超时类问题优先查布线与信号质量,而不是反复改协议参数。