坏块管理与标记策略 · 技术探讨
从「什么叫坏块」到「由谁来读这条结论」——把闪存坏块的判定、标记、替换与责任边界讲透的一份工程参考 | 编制日期 2026-10-14 | 版本号 Rev 1.0
闪存器件在出厂时就已经带着坏块,使用过程中还会继续产生新的坏块。这件事并不丢人,也不可怕——真正会出问题的是「没人管」或者「管错了人」:裸 NAND 上主机固件以为器件自己会处理,内置控制器方案上主机又想去读一个根本不存在的坏块表,这两种误判在工程现场都相当常见。
这份文档不讲器件物理,也不讲某个厂商的私有算法,只讲工程上必须回答清楚的四个问题:谁判坏、按什么判据判、把结论写到哪里、由谁来读这个结论。围绕这四问,把出厂筛选、运行时判定、标记落点、替换映射、启动场景、寿命预算、故障诊断串成一条完整链路,并明确两种不同的产品形态(裸 NAND 与内置控制器方案)在这条链上完全不同的责任边界。
结论: 坏块不是缺陷,而是闪存的标配。管理的核心永远是四问——谁判、按什么判据判、把结论写到哪里、由谁来读。 裸 NAND 里这四问全部由主机固件自己回答,判据(擦除失败、编程失败、纠错量超限)与标记落点(备用区或集中登记表)都必须由固件设计与维护; 内置控制器方案(SD NAND / eMMC 等)里坏块被控制器完全吸收,四问由控制器内部回答,主机看不到坏块,只能看到预留块消耗与健康度这类替代信号,判据与责任边界因此完全不同。 本文所有阈值、比例与次数均为行业经验区间,实际设计一律以正式 datasheet 与实测为准。
读者路径
- 先把概念钉清楚:第 1 章(成因与定义)→ 第 2 章(出厂筛选与标记)。
- 要自己写裸 NAND 的坏块管理:第 3 章(判据)→ 第 4 章(标记落点与原子性)→ 第 5 章(替换与映射)→ 第 6 章(在 FTL 中的位置)。
- 用 SD NAND / eMMC 这类内置控制器器件:第 7 章(主机能看到什么、不能看到什么)+ 第 8 章(启动场景)。
- 做寿命与容量预算:第 9 章(坏块增长与替换块预算)。
- 现场出了问题:第 10 章(按现象诊断)+ 附录 C(排查手册)。
- 只要速查表:第 12 章 + 附录 A(术语)+ 附录 B(标记与判据速查)。
1. 坏块的成因与定义
这一章先把「坏块」这个词钉死。工程现场大量的沟通事故,都来自把三种不同口径的「坏块」混着说。本章要给出的结论是:坏块本质上是一条管理结论,而不是器件的物理属性描述——这个判断决定了后面所有章节的展开方式。
1.1 什么叫坏块:从「读不出正确数据」说起
「坏块就是读不出正确数据的块」——这句话在工程上不够用,因为它把三件完全不同的事混成了一个词。实际沟通里至少有三种口径在同时使用:
- 物理口径:块内的部分存储单元已经不能可靠地保持电荷,或者阈值电压已经漂出了可读窗口。
- 操作口径:对这个块执行擦除、编程、读取时,器件返回了失败状态,或者纠错之后的结果超出了约定的可靠度。
- 管理口径:固件或控制器已经把它登记进坏块表,后续不再向它分配任何用户数据。
真正驱动系统设计的是第三种口径。前两种只是输入。一个块即使物理上已经很差,只要还没有被登记,上层就照样会把数据写进去;反过来,一个块一旦被登记,哪怕它实际上还能读出数据,也不会再被分配。坏块是一条管理结论,不是一条物理测量结论。
这也是为什么「读不出正确数据」这个起点说法需要修正:现代闪存几乎都带纠错,一次读取出原始 bit 错误是常态,不是故障。真正的起点是「纠错之后的可靠度不再满足约定」——也就是说,坏不坏不是看有没有错,而是看错到什么程度、用多大代价才补得回来。
| 口径 | 判据来源 | 谁能得到这个结论 | 对系统的直接影响 | 常见误用 |
|---|---|---|---|---|
| 物理口径 | 阈值分布 / 保持力 / 漏电 | 器件物理层,主机一般不可见 | 无直接影响,只作为失效分析输入 | 拿它去解释运行时故障 |
| 操作口径 | 擦除、编程、读取的返回状态与纠错量 | 发起操作的一方(主机固件或控制器) | 触发重试、复核与判坏流程 | 把单次失败直接当成判坏 |
| 管理口径 | 坏块登记表里是否存在该块 | 坏块表的持有者 | 决定地址分配与容量可见性 | 以为表里有就一定物理损坏 |
把这三种口径分开之后,很多争论会立刻消失。例如「这个块明明还能读,为什么要判坏」——答案是:判坏决策依据的是纠错余量已经不足以支撑下一次可靠读取,而不是「现在能不能读出来」。再例如「出厂报告说没有坏块,为什么用着用着就有了」——答案是:出厂报告是某一时刻的管理结论,不是永久保证。
1.2 出厂坏块与运行时坏块
按产生时点分,坏块只有两类:出厂坏块与运行时坏块。这个分类之所以重要,不是因为它们物理成因不同(其实高度重叠),而是因为判读者、标记落点、可信度完全不一样。
- 出厂坏块:器件在出厂前由原厂筛选出来,并把结论以某种约定写在器件内部。它的判读者是原厂的测试程序,标记在主机第一次上电之前就已经存在。
- 运行时坏块:器件交付之后,在实际使用中因擦写磨损、干扰、保持力退化等原因被判坏。它的判读者是使用方(主机固件或内置控制器),标记是在现场写下去的。
两者的关键差别在于「谁承担误判的后果」。出厂判坏判重了,代价是原厂少卖一点可用容量;出厂判坏判轻了,代价是客户现场启动失败。而运行时判坏判重了,代价是替换块被白白消耗;判轻了,代价是数据静默损坏。两种场景的代价结构完全相反,这直接决定了判据的宽松程度。
| 对比维度 | 出厂坏块 | 运行时坏块 |
|---|---|---|
| 产生时点 | 器件交付之前 | 器件交付之后的使用过程中 |
| 判读者 | 原厂测试程序 | 主机固件(裸 NAND)或内置控制器 |
| 判据 | 原厂筛选流程定义的失效门限 | 擦除 / 编程失败事件,或纠错量越限 |
| 标记落点 | 备用区就地标记,或首个块内的坏块表 | 就地标记 + 集中登记表(双写居多) |
| 标记写入者 | 原厂设备 | 现场固件 / 控制器 |
| 可信度 | 较高,但仍是某一时刻的结论 | 取决于判据与重试策略是否充分 |
| 误判代价 | 判重:可用容量减少;判轻:现场启动失败 | 判重:替换块被浪费;判轻:数据静默损坏 |
| 主机是否可见 | 裸 NAND 可见;内置控制器方案不可见 | 裸 NAND 可见;内置控制器方案不可见 |
不要以为「出厂坏块表是绝对真理」。它记录的是出厂那一刻筛选流程得出的结论,筛选流程本身有门限,门限之上判坏、门限之下放行。那些「刚好在门限之下」的块,往往就是现场最先退化的一批。出厂标记解决的是「已知问题」,不是「未来问题」。
1.3 坏块的物理成因分类
从工程处置的角度看,成因不需要细分到半导体物理层面,分成四类就够用了。分类的意义在于:不同成因对应不同的判据与不同的处置动作,胡乱套用会造成大量误判或漏判。
- 工艺与结构缺陷:栅氧缺陷、短路、开路这类硬伤。特点是一出生就有,而且不可恢复,出厂筛选能拦下绝大部分。
- 擦写磨损:隧穿氧化层在反复擦写中疲劳,绝缘能力下降,保持力逐步变差。特点是随擦写次数单调累积,可预测但不可逆转。
- 编程干扰与读干扰:对相邻单元编程、或者对同一块内其它页反复读取,会扰动未操作单元的电荷。特点是多数情况下擦除一次就能恢复,因此绝不能一发现就判坏。
- 电荷保持失效:单元漏电导致阈值随时间漂移,高温会显著加速。特点是与存放时间强相关,断电存放越久越容易暴露。
| 成因类别 | 典型机理 | 是否可恢复 | 主要暴露时点 | 对应的处置动作 |
|---|---|---|---|---|
| 工艺与结构缺陷 | 栅氧缺陷、短路、开路 | 不可恢复 | 出厂筛选、首次上电扫描 | 直接判坏并替换,无需重试 |
| 擦写磨损 | 隧穿层疲劳、保持力下降 | 不可恢复 | 擦写次数接近寿命末期 | 判坏并替换,同时计入寿命统计 |
| 编程 / 读干扰 | 相邻单元扰动、读串扰 | 多数可经擦除恢复 | 正常使用中随机出现 | 先重试与复核,确认不可恢复才判坏 |
| 电荷保持失效 | 漏电导致阈值漂移 | 取决于漂移程度 | 长时间断电存放后、高温环境 | 以纠错量越限为判据,判坏前先复核 |
1.4 坏块率与工艺、容量、叠层数的关系
坏块率不是一个可以脱离器件形态讨论的常数。它随四个方向变化,而且变化的方向在工程上是可预期的,即使具体数值必须以正式 datasheet 为准。
- 工艺微缩:单元尺寸变小,单个存储单元能容纳的电子数变少,阈值电压分布的相对展宽变大,判读窗口变窄,出错概率上升。
- 容量增大:即使单个块的失效率不变,单元总数增加也会让「至少出现一个坏块」的概率明显上升。这就是为什么大容量器件几乎不可能零坏块。
- 叠层数增加:同一片器件里垂直堆叠更多层,工艺一致性的控制难度上升,层间离散会带来额外的早期失效。
- 每单元位数增加:从每单元 1 位到多位,阈值窗口被切成更多份,每份的宽度更窄,对漂移与干扰的容忍度更低。
把这四条合起来,可以得到一条对工程有用的判断:越是大容量、高层数、高位密度的器件,越不能假设「没有坏块」。反过来说,小容量、成熟工艺、每单元位数少的器件,出厂坏块的比例通常处在很低的水平,但这依然不等于零。
| 变量方向 | 对坏块率的影响 | 机理 | 工程上的应对 | 备注 |
|---|---|---|---|---|
| 工艺越微缩 | 上升 | 单单元电子数减少,阈值分布展宽 | 判读余量收紧,纠错能力要更强 | 具体门限以 datasheet 为准 |
| 容量越大 | 出现坏块的概率上升 | 单元总数增加,基数变大 | 替换块池按容量比例预留 | 大容量器件零坏块不现实 |
| 叠层越多 | 上升 | 层间工艺一致性难度上升 | 出厂筛选更依赖原厂流程 | 与具体堆叠工艺相关 |
| 每单元位数越多 | 上升 | 阈值窗口被切得更窄 | 读判据更依赖纠错量与重试 | SLC / MLC / TLC / QLC 差异明显 |
还有一条常被忽略的推论:坏块率的变化并不改变坏块管理的必要性,只改变它的规模。工艺进步让单位容量的坏块率下降,但同期容量增长得更快,二者相抵之后,一颗器件上坏块的绝对数量通常是上升而不是下降的。因此「新工艺更可靠,所以可以少留替换块」是一个危险的想法。
对工程实践更有用的是把坏块率拆成两个量来看:单个块的失效率与整片出现坏块的概率。前者决定了运行时判坏的频率,后者决定了出厂阶段必须准备多少替换块。只盯着其中一个,都会做出错误的预算。
此外还需要注意批次离散:同一型号不同批次的坏块比例可能相差不少。因此容量预算不能只按一颗样品来定,而应当按规格书给出的上限来定,再用实测数据确认自己拿到的批次处在什么位置。
坏块率与「什么算一个坏块」的定义强绑定:有的原厂按块统计,有的按单元统计;有的把可纠错的块也算进去,有的不算。脱离定义谈数字没有意义,而定义又写在各家器件手册里。本文只给方向性判断,实际做容量预算时请直接引用所用器件的正式规格。
1.5 为什么坏块管理是标配而不是补丁
把坏块管理当成「器件坏了之后才需要的东西」,是裸 NAND 方案里最常见、代价也最高的误解。坏块管理之所以是标配,有三个很硬的理由。
- 出厂就带坏块是常态,不是异常:只要器件容量做到一定规模,零坏块在统计上就不可能稳定成立。因此上电第一件事必须是「读出厂坏块表」,而不是假设介质是好的。
- 启动代码所在的位置不能赌:启动映像如果落在坏块上,器件不是「性能下降」,而是直接变砖。这个后果的严重性决定了坏块管理必须在启动链路的最前端就位。
- 运行时坏块的产生是确定的,只是时点不确定:擦写磨损一定会发生,坏块一定会增长。问题不是「会不会有」,而是「什么时候有、有没有人接得住」。
| 设计取向 | 把坏块管理当补丁 | 把坏块管理当标配 |
|---|---|---|
| 上电初始化 | 直接挂载文件系统,出错再说 | 先读出厂坏块表,再建立可用块视图 |
| 启动映像 | 写在一个固定偏移,赌它不是坏块 | 冗余副本 + 启动前校验,坏块可跳过 |
| 运行时失败 | 返回错误码给上层,由应用处理 | 固件内部重试、复核、判坏、替换、登记 |
| 替换块 | 没有预留,坏了就少一块容量 | 按比例预留替换池,耗尽才算寿命终了 |
| 容量可见性 | 标称容量即实际可用容量 | 可用容量 = 标称容量减去出厂坏块与预留 |
| 典型后果 | 偶发变砖、静默数据损坏 | 容量略降,但行为可预期 |
1.6 本文要回答的问题与主线
全文围绕四个问题展开。这四个问题不是并列关系,而是一条链上的四个环节,任何一个环节答错,整条链就断了。
- 谁判:判坏这个动作由谁执行?裸 NAND 上由主机固件执行;内置控制器方案上由控制器内部执行。
- 按什么判据判:事件型判据(擦除失败、编程失败)与阈值型判据(纠错量超限)各自适用什么场景,判坏之前要不要重试、重试几次。
- 把结论写到哪里:就地写在块的备用区,还是集中写到一张登记表?两者如何互相校验,掉电中断时怎么保持一致性。
- 由谁来读:这份结论下次上电由谁读取、在什么时机读取、读取失败时怎么办。
本文的立场很明确:坏块不是缺陷,而是闪存的标配。讨论的重点不是「怎么消灭坏块」(做不到),而是「怎么把坏块的存在约束在一个可预期、可观测、可恢复的范围内」。沿着这条主线,第 2 章讲出厂段,第 3 到 5 章讲运行时段,第 6 到 8 章讲它在不同产品形态与场景下的表现,第 9 到 11 章讲寿命、诊断与工程落地。
| 四问 | 裸 NAND 的答案 | 内置控制器方案的答案 | 主机能看到什么 |
|---|---|---|---|
| 谁判 | 主机固件 | 器件内置控制器 | 裸 NAND 能看到;控制器方案看不到 |
| 判据 | 擦除 / 编程失败、纠错量超限 | 控制器内部判据,不对外公开 | 控制器方案看不到原始判据 |
| 写到哪里 | 备用区就地标记 + 集中登记表 | 控制器内部元数据区 | 控制器方案看不到 |
| 由谁读 | 主机固件上电时读取并重建 | 控制器内部读取 | 控制器方案只看到预留块消耗 |
2. 出厂阶段的坏块筛选与标记
这一章讲的是「主机第一次上电之前,器件里已经发生了什么」。出厂段的核心不是筛选本身,而是筛选结论以什么形式、写在什么位置、能被谁读出来。这一段决定了主机固件上电初始化时必须做什么,也决定了「出厂保证」这四个字到底保证到哪一步。
2.1 出厂筛选做了什么
原厂在器件交付前会跑一整套筛选流程,目的不是挑出「完美的器件」,而是把已知不可用的块找出来并留下记录。流程的具体环节各家不同,但骨架高度一致。
- 结构性测试:在晶圆或封装阶段剔除硬伤,这类缺陷基本不可恢复。
- 擦写与读回验证:对块做擦除、编程、读回,检查结果是否符合门限,用来抓保持力与阈值分布的问题。
- 保持力加速验证:通过高温或其它加速手段,把早期退化提前暴露出来。
- 判坏并写标记:把超过门限的块判坏,按约定写入标记。
- 生成坏块表并复核:汇总标记结果,写入约定位置,出货前再复核一遍。
| 筛选环节 | 要抓的问题 | 判据的性质 | 产出 | 局限 |
|---|---|---|---|---|
| 结构性测试 | 短路、开路、栅氧硬伤 | 事件型,一次判定 | 硬伤块清单 | 抓不到使用期退化 |
| 擦写与读回验证 | 保持力不足、阈值分布异常 | 阈值型,按门限判定 | 可疑块清单 | 门限之下的边缘块会漏 |
| 保持力加速验证 | 早期退化、漏电过大 | 阈值型,加速条件下判定 | 加速条件下的失效块 | 加速条件与现场不完全等价 |
| 判坏并写标记 | 汇总上述结论 | 管理动作 | 器件内已写好的标记 | 标记本身要能被正确读出 |
| 出货前复核 | 标记写入是否正确 | 管理动作 | 可出货的器件 | 复核只覆盖标记,不覆盖未来 |
2.2 标记写在哪里:备用区与保留块
出厂标记必须写在一个「判读者还没读它之前不会被擦掉」的位置。实际可用的位置有三类,各有各的适用条件。
- 块的备用区(页内附带的额外区域):每个页除了用户数据区,还带一小段备用区。把标记写在块内前几个页的备用区里,是最常见的做法。优点是就地可得——读这个块时顺带就能读到它自己的状态;缺点是它依附于块,块一旦被擦除,标记也一起没了。
- 保留块 / 首个块:把整片的坏块结论集中写在一两个专门保留的块里。优点是一次读出全部结论;缺点是这个块本身也要保证可靠,而且它一旦损坏就影响全局。
- 两者并用:就地标记提供就地证据,集中表提供快速全盘视图,互相校验。这是裸 NAND 上最稳健的组合。
选择落点时还有一个容易被忽略的约束:标记所在的区域不能与正常数据区共享擦除单位。如果标记写在一个会被用户数据擦除操作顺带擦掉的位置,那么一次普通写入就可能把坏块结论抹掉。这也是为什么标记通常写在块内最前面几个页的备用区——它们位置固定、容易被找到,而且在启动阶段就能读到。
还有一个现实约束是空间。备用区容量有限,除了坏块标记,通常还要放纠错校验数据与逻辑地址信息。因此在设计标记结构时,必须先确认备用区里还剩多少空间可供自己使用,而不是想写多长就写多长。
| 存放位置 | 读取代价 | 可靠性依赖 | 优点 | 缺点 |
|---|---|---|---|---|
| 块内备用区 | 随块读取,代价低 | 依赖该块前几页未被擦除 | 就地可得,不需要额外扫描 | 块被擦除后标记消失 |
| 首个块 / 保留块 | 上电一次读出全部 | 依赖该块自身出厂可靠 | 一次拿到全盘结论 | 单点失效影响全局 |
| 两者并用 | 先读表,异常时回查就地标记 | 双份,互相校验 | 恢复能力强,可交叉验证 | 占用额外空间与启动时间 |
| 文件系统层记录 | 挂载时读取 | 依赖文件系统自身完整性 | 便于日志与追溯 | 启动阶段还不可用 |
2.3 标记值的约定与判读
标记不是「某一个位等于 1」,而是一段约定:在约定位置、按约定长度、写成约定的非擦除态图案。判读时也不只看一个字节,通常是看约定范围内是否出现了非擦除态的内容。
- 为什么用「非擦除态」:闪存擦除后整片是擦除态,任何非擦除态的内容都一定是有意写进去的。用这个约定,判读逻辑就不需要知道具体写了什么值,只需要知道「有没有被写过的痕迹」。
- 为什么写在多个位置:单个字节可能因干扰或读错误被误读。写在块内前几个页的多处,判读时取多数或取「任一命中」,可以显著降低误判。
- 为什么不能只看一个值:不同厂商、不同工艺代的约定图案不一样。把某个具体值写死在自己的固件里,换一颗器件就会判错。
因此正确的做法是:按所用器件手册给出的位置与判读规则来实现,而不是照抄某个器件的值。这也是本文不列出具体标记值的原因。
| 判读环节 | 正确做法 | 常见错误 | 后果 |
|---|---|---|---|
| 位置 | 按器件手册给出的备用区偏移读取 | 照抄别的器件的偏移 | 读到的是用户数据,判读全部失真 |
| 长度与范围 | 按约定长度整段判读 | 只读一个字节 | 干扰导致误判或漏判 |
| 判读规则 | 按手册定义的「非擦除态」规则 | 写死某个具体图案值 | 换器件即失效 |
| 冗余处理 | 多处标记取一致或取多数 | 单点命中即判坏 | 一次读错误就判掉一个好块 |
| 异常处理 | 读不出标记时按「未知」处理并复核 | 读不出就当坏块 | 浪费替换块 |
2.4 首个块为什么特殊
几乎所有 NAND 器件都会对首个块给出额外保证。原因很直接:启动流程往往只能从一个固定位置开始读,如果这个位置是坏的,器件根本起不来。
- 它是启动链路的入口:很多主控的引导逻辑只认固定偏移,没有能力先扫描全盘再决定从哪里读。
- 它常常兼作坏块表的落点:整片的坏块结论需要集中存放,而能被启动流程先读到的位置非常有限。
- 原厂对它有单独的可靠性保证:通常会保证它在一定擦写次数与一定温度范围内可用。
但这份保证是有边界的:它保证的是出厂状态,不是永久状态。首个块同样会被擦写磨损,如果启动流程频繁地往它里面写日志或更新元数据,它也会退化。工程上常见的做法是尽量减少首个块的写入,或者为它的内容准备冗余副本。
| 首个块的职责 | 为什么选它 | 需要额外注意什么 |
|---|---|---|
| 启动映像入口 | 引导逻辑只能认固定偏移 | 要保证映像本身可跳过坏块 |
| 坏块表落点 | 上电最早能读到的位置 | 表本身要有副本与校验 |
| 器件标识与参数 | 便于识别器件类型 | 只读为主,避免频繁改写 |
| 出厂保证的载体 | 原厂对它有单独可靠性承诺 | 保证限于出厂状态与约定条件 |
2.5 出厂坏块表的读取时机
读取时机不是越早越好,而是要卡在「任何写操作之前、任何地址分配之前」。一旦固件先做了擦除或编程,之后再读到坏块表就已经晚了——数据可能已经写进坏块。
- 最早上电阶段:复位后、初始化控制器接口之后、建立可用块视图之前。
- 文件系统挂载之前:挂载动作本身会产生写,必须先有正确的坏块视图。
- 任何擦除 / 编程之前:这是对时机的硬要求,没有例外。
读取的代价也要计入启动时间预算:读整张表是顺序读,代价不大;但如果需要全盘扫描来重建表,代价就取决于容量。这就是为什么出厂表要集中存放——它把「上电时的全盘扫描」变成了「一次小范围顺序读」。
| 时机 | 该做什么 | 不该做什么 | 代价 |
|---|---|---|---|
| 接口初始化后 | 读取出厂坏块表并建立可用块视图 | 直接初始化文件系统 | 一次小范围顺序读 |
| 挂载文件系统前 | 校验表与就地标记是否一致 | 跳过校验直接挂载 | 校验需要额外读,可控 |
| 任何写操作前 | 确认目标块不在坏块表内 | 先写再查表 | 无,硬要求 |
| 运行期定期 | 增量校验,异常时局部重扫 | 每次上电都全盘重扫 | 全盘重扫耗时随容量增长 |
2.6 出厂标记的可信边界
最后必须说清出厂标记不保证什么。它是一份很有价值的输入,但把它当成绝对真理会付出代价。
- 它是某一时刻的结论:筛选门限之下的边缘块会放行,这批块正是现场最先退化的。
- 它不覆盖使用期产生的坏块:运行时坏块必须由使用方自己判、自己记。
- 它不保证标记本身永不出错:标记写在闪存里,一样会受干扰与保持力影响,所以需要副本与校验。
- 它不等于容量保证:可用容量还要扣除预留的替换块池与元数据区。
| 出厂标记保证的 | 出厂标记不保证的 | 使用方必须补上的 |
|---|---|---|
| 交付时已知坏块已被标出 | 使用期新产生的坏块 | 运行时判坏与标记流程 |
| 标记写在约定的可读取位置 | 标记本身永不出错 | 副本、校验与一致性恢复 |
| 首块在约定条件下可用 | 首块永久可用 | 减少首块写入 + 冗余副本 |
| 坏块比例在原厂门限内 | 具体坏块数恰好等于表中数量 | 上电复核与局部重扫能力 |
3. 运行时坏块的判定
出厂标记只覆盖了「已知问题」。器件交付之后新产生的坏块,必须由使用方自己判。这一章解决的是四问里的第二问:按什么判据判——哪些事件值得触发判坏流程,哪些只值得重试,以及判错了要付出什么代价。
3.1 哪些事件会触发判坏
运行时会出现各种各样的异常,但绝大多数异常都不应该直接判坏。真正值得进入判坏流程的输入只有几类。
- 擦除失败:对该块下发擦除命令后,器件返回失败状态,或者擦除完成后检查发现并非擦除态。
- 编程失败:对该页下发编程命令后返回失败,或者编程后读回的内容与预期不一致。
- 读纠错量越限:读取时纠错过程消耗的量超过约定门限,说明这个块的余量已经很少。
- 状态机异常:器件长时间不返回就绪、命令超时、状态位出现异常组合。
- 上层校验发现不一致:文件系统或应用层的校验和、冗余校验发现数据不可信。
注意最后两类:它们本身不是闪存层的判据,而是上层提供的线索。它们可以触发复核,但不应该直接触发判坏——因为失效的可能不是介质,而是链路、时序或者软件本身。
| 触发事件 | 来源层次 | 是否直接进入判坏 | 进入判坏前的必经动作 | 备注 |
|---|---|---|---|---|
| 擦除失败 | 器件操作层 | 重试后可进入 | 重试擦除、擦除后校验 | 事件型,判据最硬 |
| 编程失败 | 器件操作层 | 重试后可进入 | 重试编程、读回比对 | 事件型,但受干扰影响大 |
| 读纠错量越限 | 器件操作层 | 复核后可进入 | 换参考重读、多次确认 | 阈值型,门限决定松紧 |
| 状态机超时 / 异常 | 接口层 | 不应直接进入 | 复位链路、重新初始化 | 失效可能在链路而非介质 |
| 上层校验不一致 | 文件系统 / 应用层 | 不应直接进入 | 定位到具体块后再复核 | 只作为线索使用 |
3.2 擦除失败的判定
擦除失败是最硬的一类判据。原因是擦除不依赖读判读窗口,也不受纠错能力影响:一个块如果连擦除都完不成,或者擦完之后仍不是擦除态,那它基本没有继续使用的价值。
- 返回失败状态:器件在擦除过程中或结束后报出失败,这是最直接的判据。
- 擦除后校验不通过:擦除完成但检查发现存在非擦除态的内容,说明部分单元已经无法被拉回擦除态。
- 重试仍然失败:单次失败可能是瞬时干扰,重试一次仍然失败才构成可信判据。
擦除失败的处置路径最短:重试一次 → 仍失败 → 判坏 → 标记 → 替换。不需要复杂的纠错统计,因为它本身就是事件型判据。
不过「最硬」不等于「不需要重试」。擦除操作同样可能受到电源波动、时序异常、器件正处于 busy 状态等瞬时因素影响。工程上仍然建议至少重试一次并确认状态,把「器件当时根本没准备好」这类假性失败过滤掉,再进入判坏。
还有一点值得注意:擦除失败发生的位置很重要。如果失败集中在某个固定区域,往往指向工艺或结构性问题;如果分散在全盘且随写入量上升,则更像磨损导致的整体退化。把失败位置记录下来,对后续定位很有帮助。
3.3 编程失败的判定
编程失败同样属于事件型判据,但它比擦除失败更容易被干扰污染,因此处置时要更谨慎一步。
- 编程过程中报失败:直接反映了器件对该页的编程无法完成。
- 编程后读回不一致:说明该页没有写到位,属于编程/验证类失败信号;保持力退化则表现为存放一段时间后纠错量上升,两者要分开归因。
- 同一块内连续多页失败:比单页失败更可信,往往指向块级问题而非页级偶发。
编程失败的处置路径是:重试 → 换页重试(若该块还有可用页)→ 读回校验 → 仍失败则判坏。关键差别在于:编程失败允许「换页」这个中间动作,因为一个块内某一页坏了,不等于整个块都不能用了。
这里也引出了第 5 章要讲的块级替换与页级重映射的取舍:如果只做块级替换,一页坏就废掉整个块,代价偏大;如果做页级重映射,管理复杂度会明显上升。
3.4 读错误:纠错阈值型判坏
读错误是最容易被误用的一类判据。必须反复强调:闪存读出原始 bit 错误是常态,不是故障。真正有意义的是「纠错消耗了多少余量」。
- 纠错量在低位:正常,无需任何动作。
- 纠错量接近门限:值得关注,说明这个块的余量在消耗,可作为健康度统计的输入。
- 纠错量超过门限:进入复核流程,考虑判坏。
- 纠错无法完成:这是最强的读类判据,相当于事件型。
阈值型判据的关键在于门限怎么定。定得太松,坏块会一直被用到数据真的出错;定得太紧,替换块会被迅速消耗完。工程上通常的做法不是只看一次读数,而是看趋势:同一块的纠错量是否在持续上升。
| 纠错量所处区间 | 含义 | 处置动作 | 是否计入健康度统计 |
|---|---|---|---|
| 远低于门限 | 正常工作区间 | 无动作 | 可抽样记录 |
| 接近但未超过门限 | 余量在消耗 | 记录趋势,暂不判坏 | 计入,作为趋势输入 |
| 超过门限但可纠错 | 余量已经很少 | 换参考重读复核,再决定是否判坏 | 计入,并提升告警级别 |
| 无法完成纠错 | 已不可靠 | 直接进入判坏与替换流程 | 计入,并记为一次可靠性事件 |
读干扰、温度漂移、瞬时电源噪声都可能让某一次读取的纠错量偏高。单次高纠错量不构成判坏依据,它只构成「值得再看一眼」的依据。正确的顺序是:换参考重读 → 多次确认 → 看同块历史趋势 → 才考虑判坏。跳过这些步骤的固件,往往会在器件还很健康的时候就把替换块耗光。
3.5 事件型判坏与阈值型判坏的差别
把判据分成事件型与阈值型,不只是分类游戏,它直接决定了固件的代码结构。两类判据的处理流程差别很大,混在一起写几乎一定会出错。
- 事件型:输入是离散的(失败 / 成功),处置是「重试固定次数 → 仍失败即判坏」。逻辑简单,路径确定。
- 阈值型:输入是连续的(纠错量),处置是「多次采样 → 看趋势 → 超过门限才判坏」。逻辑复杂,需要状态记录。
还有一个容易被忽略的差别:事件型判据的误判方向偏「漏判」,阈值型判据的误判方向偏「误判」。事件型只有在器件真的报失败时才动作,所以容易漏掉渐进退化;阈值型只要门限设得稍低就会频繁触发,所以容易误杀好块。
| 对比维度 | 事件型判据 | 阈值型判据 |
|---|---|---|
| 输入形态 | 离散状态:失败或成功 | 连续量:纠错量、重试次数等 |
| 典型来源 | 擦除失败、编程失败 | 读纠错量越限、磨损计数越界 |
| 是否需要门限 | 不需要,状态本身即结论 | 需要,门限决定松紧 |
| 是否需要历史记录 | 不需要 | 需要,用于看趋势 |
| 重试策略 | 固定次数重试 | 换参考重读 + 多次采样 |
| 误判倾向 | 偏漏判,渐进退化抓不到 | 偏误判,门限低则误杀好块 |
| 典型处置路径 | 重试 → 仍失败 → 判坏 | 采样 → 复核 → 越限 → 判坏 |
| 适用阶段 | 出厂筛选与运行时都适用 | 主要在运行时使用 |
3.6 判坏之前的重试策略
重试不是「再试一次」这么简单,它是一套逐级加重的阶梯。设计原则很明确:先轻后重,每一步都留记录,顶端之前留一级复核。
- 原样重读:代价最低,用来排除瞬时噪声。
- 调整读参考后重读:阈值漂移时,把读参考电压移到更合适的位置往往就能读出来。
- 用更强的纠错处理:如果器件支持不同强度的纠错,用更强的档位再试一次。
- 擦除后重写并校验:这一步已经会消耗一次擦写寿命,只有在前面都失败时才用。
- 判坏并写入标记:阶梯顶端,不可逆。
比阶梯本身更重要的是记录。固件应该记录「哪一步解决了问题」:如果大量错误在第 2 步就被解决,说明阈值漂移是主因,门限可以放宽;如果经常要走到第 4 步,说明器件磨损已经比较严重。
| 阶梯层级 | 动作 | 代价 | 能解决什么 | 记录什么 |
|---|---|---|---|---|
| 第 1 级 | 原样重读一次 | 一次读时间 | 瞬时噪声、偶发干扰 | 重读即恢复的次数 |
| 第 2 级 | 调整读参考后重读 | 几次读时间 | 阈值漂移、温度影响 | 需要调参考的块分布 |
| 第 3 级 | 更强纠错处理 | 一次读时间 + 算力 | 纠错量偏高但可纠 | 纠错量分布与趋势 |
| 第 4 级 | 擦除后重写并校验 | 一次擦写寿命 | 干扰累积、可恢复的软失效 | 走到本级的次数 |
| 第 5 级 | 判坏并写入标记 | 消耗一个替换块 | 已确认不可恢复 | 判坏总数与位置分布 |
3.7 误判的代价
判坏这个动作是不可逆的:一旦把某个块登记为坏块并拿替换块顶上,这个块就基本退出了服务(除非专门设计回收机制)。因此误判的代价必须被认真计算。
- 判重了(误杀好块):直接代价是消耗一个替换块;间接代价是替换池提前耗尽,器件可用寿命被人为缩短。
- 判轻了(放过坏块):直接代价是数据可靠性下降;最坏情况是静默数据损坏——数据写进去了、读出来也没报错,但内容已经不对。
两种代价不对称,而且不对称的方向取决于应用场景。消费类场景通常更能接受「少一点容量」,工业与车载场景则更不能接受「数据悄悄错了」。因此判据的松紧不是一个技术参数,而是一个产品决策。
| 误判方向 | 直接代价 | 间接代价 | 最坏后果 | 更容易发生在 |
|---|---|---|---|---|
| 判重(误杀好块) | 消耗一个替换块 | 替换池提前耗尽,寿命缩短 | 器件在标称寿命前就宣告写满 | 阈值型判据门限过紧 |
| 判轻(放过坏块) | 该块继续承载数据 | 纠错余量被持续消耗 | 静默数据损坏,上层无感知 | 事件型判据缺少复核 |
| 标记写错位置 | 表里记录与实际不符 | 后续地址翻译出错 | 数据写到已被判坏的块上 | 标记落点设计不严谨 |
| 标记未持久化 | 掉电后结论丢失 | 下次上电重新踩同一个坑 | 反复在同一坏块上失败 | 忽略原子性与掉电场景 |
4. 运行时标记写到哪、怎么写
判坏只是得到一个结论,真正难的是把这个结论安全地存下来。这一章解决四问里的第三问:写到哪里、怎么写,重点落在原子性与掉电一致性——这是裸 NAND 方案里最容易出问题、也最少被认真设计的一环。
4.1 就地标记与集中登记表
运行时标记同样有两类落点,而且强烈建议两者并用。
- 就地标记:在该块自己的备用区写入约定图案。优点是就地可得,读这个块时就顺带知道它的状态;缺点是分散在全盘,要拿到全盘结论就得全盘扫描。
- 集中登记表:在一处集中记录所有坏块。优点是上电一次读出全部结论;缺点是它自己也是一份需要保护的数据,写入时同样面临掉电风险。
两者并用的价值在于互为备份:登记表丢失可以靠就地标记重建,就地标记丢失可以靠登记表恢复。只有一份的设计,在掉电场景下几乎一定会出问题。
| 方案 | 上电代价 | 掉电后恢复能力 | 占用空间 | 适用建议 |
|---|---|---|---|---|
| 只做就地标记 | 需要全盘扫描才能重建结论 | 强,标记本身分散且就地 | 极小,随块分布 | 小容量器件可接受 |
| 只做集中登记表 | 一次顺序读拿到全盘结论 | 弱,表本身损坏即全盘未知 | 随坏块数增长 | 不推荐单独使用 |
| 就地标记 + 登记表 | 先读表,异常时回查就地标记 | 强,两份可互相校验与重建 | 两者之和 | 推荐做法 |
| 登记表 + 多份副本 | 读主表,失败读副本 | 较强,取决于副本份数 | 随份数线性增长 | 对启动时间敏感时适用 |
4.2 标记的原子性:掉电中断会怎样
「原子性」在这里的含义很朴素:一次标记操作,要么完全生效,要么完全不生效,不能停在中间状态。而现实是:写标记这件事本身至少包含两个独立的写动作(就地标记 + 登记表更新),掉电可以在任意时刻发生。
- 掉电在写就地标记之前:两份都没有,块仍是好块,下次还会被用到,会再失败一次。代价最小。
- 掉电在就地标记之后、登记表之前:就地标记已生效,登记表没更新。两份结论不一致,这是最需要处理的情形。
- 掉电在登记表之后:两份都生效,状态一致,代价只是损失一次写。
应对思路不是「让写操作变原子」(做不到),而是让不一致状态可被检测到、可被收敛。具体做法是:给标记写一份可校验的结构(顺序号或校验字段),上电时比较两份的顺序号,以较新的一份为准,并把较旧的一份补齐。
| 掉电发生的时刻 | 就地标记状态 | 登记表状态 | 上电后的处理 | 代价 |
|---|---|---|---|---|
| 写就地标记之前 | 未写 | 未更新 | 当作未判坏,下次重新走判坏流程 | 一次重复判坏 |
| 就地标记之后、表之前 | 已写 | 未更新 | 以就地标记为准,回补登记表 | 一次回补写 |
| 表更新之后 | 已写 | 已更新 | 状态一致,无需处理 | 无额外代价 |
| 正在写就地标记 | 可能部分写入 | 未更新 | 校验失败则视为未写,重新写一次 | 一次重写 |
4.3 标记副本与一致性恢复
一致性恢复的目标不是「不出错」,而是「出错之后能收敛到一个确定的状态」。常用的手段有三个。
- 顺序号:每份标记带一个单调递增的序号,比较两份的序号就知道谁更新。
- 校验字段:让半写状态可被检出,避免把写了一半的内容当成有效结论。
- 先写新、后失效旧:更新登记表时先把新版本写好,再把旧版本标记为失效,保证任何时刻至少有一份完整可用。
恢复流程应当固化成一个幂等的函数:上电时无条件调用,无论上次掉电发生在哪一步,调用之后系统一定处于一致状态。把恢复逻辑写成「针对某种特定掉电场景的处理」,一旦出现没预料到的场景就会卡死。
4.4 什么时候需要全盘重扫
全盘重扫是最彻底的恢复手段,也是代价最高的。它的触发条件必须明确,否则要么频繁拖慢启动,要么该扫的时候没扫。
- 登记表所有副本都校验失败:这是最强的触发条件,说明集中结论已经不可信。
- 就地标记与登记表冲突且无法判定新旧:两份结论矛盾又分不出先后,只能回去挨个看。
- 首次使用一颗新器件且没有可信表:某些情况下需要建立基线视图。
- 怀疑介质整体退化:例如长期高温存放后重新上电。
不建议把全盘重扫作为常规启动动作。它的耗时随容量增长,在大容量器件上会明显拖慢启动。合理的设计是:常规启动只读表,把全盘重扫留给异常分支,并且允许它在后台进行、先用表里的视图把系统拉起来。
| 触发条件 | 是否必须阻塞启动 | 代价 | 建议做法 |
|---|---|---|---|
| 登记表所有副本校验失败 | 是,否则没有可用视图 | 一次全盘读 | 阻塞启动,扫完再挂载 |
| 就地标记与表冲突且无法判定 | 是 | 一次全盘读 | 以就地标记为准重建表 |
| 首次使用新器件 | 视情况 | 一次全盘读 | 可在后台完成,先用表启动 |
| 长期高温存放后重新上电 | 否 | 一次全盘读 | 后台扫描,前台照常用表 |
| 常规上电 | 否 | 极小 | 只读表与副本,不做全盘扫描 |
4.5 标记信息的存放位置约束
标记落点不是随便选的,它受三条硬约束限制。
- 必须在任何擦除之前可读:如果标记所在的区域可能在正常操作中被擦掉,这份标记就没有意义。
- 必须能被判读者在最早的上电阶段读到:如果读标记需要先初始化文件系统,那它在启动场景里就不可用。
- 不能放在它所管理的介质之外的不可靠位置:把坏块表放在另一颗同样会坏的器件上,只是把风险搬了个家。
这三条约束合起来,解释了为什么块内备用区 + 保留块会成为主流组合:备用区满足「就地可得」,保留块满足「最早可读」,两者都在同一颗器件内部,不需要外部依赖。
如果一份坏块标记需要依赖另一份数据的正确性才能被读到,那它就是不可靠的。检查自己的设计时可以问一句:「如果文件系统损坏、如果元数据区坏了,这份标记还能被读出来吗?」答案如果是否定的,这份设计在启动场景里就是有风险的。