news 2026/10/20 16:32:25

NAND坏块管理与BBT核心逻辑:从原理到工程落地指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NAND坏块管理与BBT核心逻辑:从原理到工程落地指南

做嵌入式存储相关的开发,不管是写裸NAND驱动、搭文件系统,还是选型eMMC芯片,迟早都会撞上一个绕不开的话题——坏块管理。这个词听起来像给数据存储做“安检”,但它实际上是贯穿NAND Flash整个生命周期的基本功。我刚开始接触的时候,也以为坏块管理就是单纯把坏块找出来做个标记,后来在轻视它这件事上栽了不少跟头。这篇内容就围绕BBT(坏块表)和坏块管理的核心逻辑展开,用贴近实操的方式说清楚:NAND为什么必然会产生坏块、BBT是怎么运作的、不同方案下坏块管理到底该怎么落地。适合刚入行的嵌入式软件工程师,也适合准备把存储方案做到产品级的开发者。

1. NAND的物理宿命:为什么坏块是躲不掉的

1.1 一个数据单元是怎么“累死”的

NAND Flash的存储单元本质上是一个浮栅晶体管,数据是靠电荷存在浮栅里来表达的。写入时,电子被强电场穿过隧穿氧化层注入浮栅;擦除时,又用相反的电压把这些电子拉出来。问题就出在这个“穿来穿去”的过程上。

每做一次编程/擦除循环,隧穿氧化层就会被“搓”掉一层。次数多了,氧化层变薄、漏电、锁不住电荷,这个单元的可靠性就越来越差,直到彻底无法判断里面存的是0还是1。这就像一张纸反复铅笔写、又反复擦掉重写,擦到最后纸面起毛、破洞,再怎么写也写不成型了。

这个过程不是说整个芯片一起坏,而是按块(Block)为单位逐步恶化的。NAND的最小读单元是页(Page),最小擦除单元是块(Block),一块通常包含64、128甚至几百个页。只要一个块里的关键单元坏了,导致这个块没法被可靠擦除或写入,那这个块基本上就宣告报废。

1.2 坏块的正式判定:不是你想坏就能坏

工程上判断一个块是不是坏块,靠的是操作结果和ECC校验,而不是靠肉眼观察:

  • 编程/写入失败:往某个页写数据时,状态寄存器回报编程失败。这类失败通常意味着整个块已经处于不可用状态。
  • 擦除失败:擦除操作回报失败,这个块基本无药可救。
  • ECC纠错无法容忍:读取时数据错误比特数超过ECC能力上限,说明这个块的电荷保持能力已经崩了。

坏块管理的核心对象,就是以块为单位的这些东西。有不少人第一次接触NAND时习惯把坏块理解成“硬盘的坏道”,这个类比其实不太准。坏道往往是一个扇区坏了,还能隔离掉;而NAND的坏块是物理上“一整块”不可用,而且坏掉的块数量会随着使用时间持续增长,不是一个恒定值。

2. 出厂坏块与运行期坏块:两种语境下的同一件事

2.1 出厂坏块:原厂已经帮我们做了标记

NAND出厂时就不是完美的。原厂在晶圆制造、切割、封测阶段,就能检测出一部分不合格的块。这不是成品没做好,而是工艺上允许有一定比例的不良品存在。比较有意思的是,随着制程不断微缩,出厂坏块的比例反而在上升。以前SLC时代,出厂坏块可能也就千分之几;到了TLC、QLC时代,1%~2%甚至更高的出厂坏块比例都很常见,原厂会在芯片出厂前把这些坏块状态“烧”到一个固定位置。

最常见的标记方式,是在块内第一个页的OOB区域(带外区域)固定偏移位置写入非0xFF的值。比如某个行业约定俗成的规则,把OOB里特定字节写成0x00代表坏块,保留0xFF代表好块。原厂还会多写几处重复标记,防止标记位置本身被破坏后读不出来。

2.2 运行期坏块:系统运行中的拦截信号

出厂坏块是“天生的”,运行期坏块则是“用坏的”。系统运行中主要靠两类信号发现新坏块:

第一类是操作层回报的硬错误。比如文件系统往某个块发擦除命令,返回擦除失败;往某个页发写命令,返回编程失败。这些都是硬性的物理故障信号,基本不用怀疑,直接标记坏块就行。

第二类要靠ECC状态位来判断。NAND的页读取通常会返回ECC纠错情况,如果当前页的错误比特数已经逼近甚至超过ECC阈值,这通常是一个预警信号。行业里一般不会马上把它当成坏块处理,而是尝试把这个块的数据搬走、做一次擦除,如果擦除还失败再确定坏块。这个“二次确认”流程能有效减少误判,毕竟有些时候只是电荷发生暂时性偏移,重新擦写后还能继续用。

2.3 两种坏块的管理权重差异

从管理策略上看,出厂坏块属于静态信息,产品出厂前处理好就行;运行期坏块则是动态事件,整个生命周期都要盯。下面这个表格可以把两者的差异看得很清楚:

对比维度出厂坏块运行期坏块
来源制造环节的工艺缺陷擦写磨损、电荷泄漏、环境干扰
特点数量固定,出厂时已确定数量随P/E次数增长而增加
获取方式读OOB区域的原厂标记操作失败、ECC阈值告警
处理阶段设备初始化阶段扫描并建立BBT运行中实时监测并动态更新BBT
误判风险低,标记固定较高,需要二次确认

很多嵌入式工程师只重视出厂坏块的扫描,对运行期坏块的动态更新不够上心,这是后面会踩大坑的地方。

3. BBT的内核逻辑:一张表如何撑起整片存储的可靠性

3.1 BBT到底记了什么

BBT的全称是Bad Block Table,坏块表。它的本质很简单:用一张结构化的记录,标记出哪几个块是坏的,哪几个块是好的。

常见的表项记录内容有这么几类:

  • 块编号或物理地址:让人能定位到具体哪个块。
  • 坏块状态:0表示好块,1表示坏块,也可以用枚举值细分坏的原因。
  • 发现时间或原因标识:像是出厂坏块还是运行期新损坏,方便后期做统计分析和寿命预测。
  • 保留字段:为以后扩展留位置,比如记录被替换的备用块映射关系。

BBT可以有不同形态。最简单的是位图形式,一个bit对应一个块;稍微复杂一点的是引导区形式,记录每个块的详细状态。位图形式省空间、查询快,但信息量少;引导区形式占用大一点,但能承载更多现场信息。实际产品里两者经常结合使用,位图做索引、详细记录做兜底。

3.2 BBT自己放在哪里才安全

这里的核心矛盾是:BBT是用来记录坏块的,但存放BBT的区域自身也存在坏块风险。如果把BBT放在固定的一个块里,这个块坏了,BBT就全丢了。行业里通用的做法是冗余副本加分散放置:

  • 多个副本:通常存放2到4份,分散在不同Plane、不同Die或不同分区。
  • 写入仲裁:每次更新时按序写入副本,并带序列号或CRC校验。读取时取校验通过且序列号最新的那份。
  • 动态搬迁:如果发现某个BBT存放块开始出现ECC告警,就主动把BBT搬到一个健康的保留块里,并更新指针。

这些思路和数据库的WAL(预写式日志)有点相似,核心思想都是“不能把鸡蛋放在一个篮子里”。有些方案甚至会把BBT管理器体本身做成一个小文件系统,专门管理这些系统块,这样可扩展性更强,但复杂度也高不少。

3.3 BBT什么时候更新、怎么防止更新丢一半

BBT不是启动时读一次就完事,它在运行中是持续演变的。触发更新的典型时机包括:

  • 某个块在擦除、编程时失败,确认坏块后立即更新。
  • 后台巡检发现ECC错误率异常升高,完成了坏块替换后更新。
  • 磨损均衡模块切换活跃块,更新映射关系。

这里最容易出问题的是更新过程中的掉电。假如正在往BBT块里写新数据,写到一半突然断电,老数据被覆写了一部分,新数据也没写完,BBT直接就“残废”了。行业里的通用做法是双页交替写:保留上一版有效BBT,把新版本写在另一个位置,写完后再做一个切换标记。下次启动时先读切换标记,再决定用哪个版本。如果两个版本都校验失败,那就只能回到出厂扫描状态——意味着所有运行期的坏块记录全部丢失,系统不得不重新全盘扫描,这在量产设备里属于重大事件。

4. 坏块管理的三种主流策略与选择权衡

4.1 跳过坏块:最朴素也最常见

跳过策略很好理解,就是通过BBT查询到坏块地址后,在寻址时直接跳到下一个可用块。这种方案不需要维护复杂的映射表,逻辑简单、开销低,对工程量有限的产品很友好。

但它的缺点也很明显。因为物理地址的连续性破坏了,逻辑块号和物理块号之间无法一一对应。如果上层文件系统直接按连续物理地址计算偏移,就会踩到坏块上。所以跳过策略一般得配合某种地址转换层,至少是一个简单的“逻辑块号到物理块号”的重排序表。

4.2 重映射与保留块池:FTL层的基本功

更专业的方案是引入FTL(闪存转换层),把逻辑块号映射到物理块号,并在芯片出厂时就划出一块保留块池。当某个活跃块损坏时,FTL从保留块池里选一个新块,把旧块里的有效数据搬过去,然后更新映射表。这个过程对上层来说是透明的。

这套机制最大的优势是分布更均衡,不会因为个别坏块导致空间利用率断崖式下降。但代价是FTL本身复杂度高,映射表也要占用一部分NAND空间,还得时刻处理映射表的持久化问题。尤其到了掉电恢复场景,如果映射表丢了,后果比BBT丢失还严重,整个用户文件系统都可能无法挂载。

4.3 策略选择的现实权衡

我做过几次选型对比,简单总结一下三种策略的适用场景:

策略实现难度NAND利用率运行开销适合场景
跳过坏块低中等低裸NAND+自主文件系统、数据可重建场景
地址重映射中较高中需要随机读写性能的小型设备
保留池+磨损均衡高高较高追求长寿命、高可靠性的产品级方案

需要注意的是,选择策略时不能只盯着“坏块怎么办”这一个问题,还得和磨损均衡、掉电保护、垃圾回收这些功能综合起来考虑。比如一个产品的数据更新频率很高,那磨损均衡的重要性可能远大于坏块替换;反过来,如果产品生命周期短、写入量少,复杂的FTL层反而可能因为映射表写入放大而加速磨损。

5. 裸NAND与带主控存储:玩法的分水岭

5.1 裸NAND:从启动阶段就要自己扛

如果方案用的是裸NAND(没有内置主控的Flash芯片),那坏块管理就是嵌入式软件自己躲不开的工作。难点在于,从Boot阶段开始就得处理坏块,因为引导代码本身也放在NAND里。

常见做法是把引导代码分成多份冗余存放,BootROM加载第一份失败后尝试第二份。同时,Boot阶段的代码要尽量小,能塞进几个块中不跨坏块。等主系统运行起来,再加载完整BBT进入工作模式。曾有团队在某个项目里把Boot分区紧挨着一块出厂坏块放置,反复测试几十次没问题,结果量产某批次芯片因为坏块位置偏移,直接导致一批设备刷机后起不来。这就是跳过策略下没做冗余引导的典型事故。

5.2 eMMC/UFS:主控替你扛,但你仍要懂

对于eMMC和UFS这类带主控的存储,坏块管理和磨损均衡大多数已经由内部FTL处理了。上层开发者不用再去扫描出厂坏块,也不需要维护BBT,看起来很省心。但省心不意味着可以无知。

带主控的存储依然有寿命上限,而且内部容量里面还藏着一块保留空间,专门给坏块替换和磨损均衡用。当主控内部保留块消耗殆尽,芯片就会出现大量写入失败或强制只读。所以就算用eMMC,我也建议在产品里保留SMART数据的读取通道,监控平均擦写次数和重映射块数。不然设备突然间批量变砖,排查起来很痛苦。

5.3 几个容易混淆的判断

我经常看到有人把“裸NAND”和“eMMC”混为一谈,这里有几个判断上的关键分歧点:

第一,裸NAND没有统一的状态汇报接口,坏块信息只能靠上层扫描或原厂标记;eMMC有标准状态寄存器,可以查询寿命和重映射情况。第二,裸NAND的坏块管理策略完全取决于自己代码的健壮性,换了主控方案后代码基本可以复用;eMMC的方案则依赖于主控厂商实现质量,换供应商后行为可能有细微差异。第三,裸NAND对掉电非常敏感,映射表丢失风险高;eMMC由于有内置电容和固件保护机制,掉电容错性要好得多,但也不是100%免疫。

6. 实战里最容易踩的四个坑

6.1 坏块表更新的掉电瞬间

我们在某个模拟项目X里调试了很久,最后发现问题出在BBT更新流程。当时为了图省事,BBT只有一个副本,每次更新都是直接擦除原块再写新内容。结果设备在频繁掉电测试下,大概每几百次就会遇到一次BBT校验失败。原因很简单,擦除原块后新数据还没写完,电就断了,整个表变成了空白。

后来改成双副本轮流写,并增加“当前有效版本”标志位,这个问题基本清零。经验是:坏块表的更新流程里必须有一个原子切换动作,不能存在“中间状态可被识别为有效”的窗口。

6.2 上电全盘扫描的正确打开方式

每次上电都做全盘扫描最稳妥,但代价很大。一片中等容量的MLC NAND,全盘读一遍可能要好几十秒,很多产品根本接受不了这个启动时间。

更实用的办法是把扫描拆成两级:启动阶段只扫描出厂坏块标记区和最近记录的BBT,快速进入工作状态;后台再慢慢做全量巡检,发现新坏块再动态更新。当然,前提是文件系统访问NAND的路径本来就要走统一的坏块查询接口,否则后台扫描还没跑完,某个坏块可能已经被读过了。

6.3 出厂坏块标记的重复判断陷阱

原厂标记一般在OOB区域重复写好几份,目的是防止某个标记位置被意外改写。但我见过一些代码只判断第一个标记位置,一旦那个位置损坏,明明是坏块却被当成好块,写入后就出现数据打水漂的情况。

正确做法是检查多个标记位置,并设定判定规则:比如“任意一个标记为非0xFF即视为坏块”或者“多数标记一致才判定”。后一种更稳健,但要注意有些原厂在擦除后会把标记区一起擦成0xFF,这会导致“重复擦除后标记消失”的假象。所以这块逻辑一定要基于原厂手册仔细核对,不能想当然。

6.4 放久了不读的盘,坏块会“变多”

这是我在售后数据里发现的一个反直觉现象。一批设备存放了几个月后开机,发现坏块数量比出厂时多了不少。起初怀疑是器件批量问题,后来排查才发现是电荷泄漏。长时间断电状态下,NAND单元里的电荷会慢慢衰减,导致读取时ECC错误变多。如果错误率超过阈值,巡检程序就会把这类块标记成坏块。

处理办法有两条:一是产品在长期存储前,尽量把数据重新刷写一遍,给电荷“续个命”;二是巡检判坏时不要把一次性读取错误直接定性为永久坏块,可以加一个“擦除-重写-再验证”的流程。很多时候,擦写一次之后块又能正常用了。

从我个人的实际经验看,坏块管理做得好的项目,不一定代码写得有多花哨,而是把每一条边界条件都想清楚了。比如掉电时序、扫描时机、误判保护、冗余策略,这些环节单独看都不难,难的是一起发生时系统依然能自洽。刚开始做嵌入式存储时,我也犯过“一张表打天下”的毛病,总觉得坏块管理就是启动时扫一次、查表跳过去就行,后来面对现场设备才明白,真正的可靠性是靠一层层冗余和兜底逻辑堆出来的。如果你正在设计存储方案,不妨先把这篇文章里提到的边界场景列成一份检查清单,逐条对照自己的实现,大概率能省下不少返工的功夫。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 16:32:08

前缀表达式求值怎么算?栈与操作数顺序的完整拆解

刷题时碰到“求前缀表达式的值”这种题,第一反应往往是把中缀转后缀那套逻辑反过来套,结果一写就错。前缀表达式也叫波兰式,运算符写在前面,操作数写在后面,比如 1 2 就是 1 2 。这道题在OJ上分值常是25分&#…

作者头像 李华
网站建设 2026/10/20 16:31:35

把数学思维练成本能:程序员必备的五大核心思维模式

写了这么多年代码,有个现象一直让我很在意:同样是遇到线上故障,有的人能一边看监控一边快速缩小范围,有的人却只能盯着日志逐行硬翻。前者也不见得读过更多源码,后者也不是不努力。区别往往在思维方式——数学思维是否…

作者头像 李华
网站建设 2026/10/20 16:30:05

直流电机双闭环调速Simulink仿真:从参数计算到PI整定的完整流程

做直流电机双闭环调速仿真,最常见的翻车方式不是模型搭不出来,而是参数全靠猜。我见过不少人在Simulink里把电流环和转速环的PI参数随手填成1和0.1,波形一振荡就说系统不稳定,实际上连电流反馈系数是多少都没算过。合理的流程应该…

作者头像 李华
网站建设 2026/10/20 16:30:01

数字仿真实战指南:从建模到后处理的避坑经验与工程技巧

1. 数字仿真到底是什么:一个老工程师的实话干了十几年结构设计和仿真分析,我最大的体会是:数字仿真早就不是“锦上添花的加分项”,而是产品开发里“不上不行的关键环节”。以前我们设计一个零件,靠的是经验公式加安全系…

作者头像 李华
网站建设 2026/10/20 16:29:58

复杂度分析实验:用C++实测比较查找与排序算法的增长趋势

如果只让我从数据结构课里挑一个必须亲手做完的实验,我会选复杂度分析。原因很简单:其他实验是在教你怎么把代码写出来,而这个实验是在教你怎么判断代码写得好不好。可惜不少同学把这一题当成“画图题”,复制一段排序代码&#xf…

作者头像 李华
网站建设 2026/10/20 16:29:49

SpringBoot中JUnit5+Mockito单测体系搭建实战与避坑指南

我刚接触单元测试那会儿,心里其实挺抵触的。总觉得业务都写不完了,哪来的时间写测试?直到后来在某个项目里改一个公共校验方法,本地跑了十分钟都没问题,一上线就把一个老接口的数值精度带歪了,排查到凌晨才…

作者头像 李华