☰
NAND Flash坏块管理指南:从物理原理到工程实践
2026/9/28 1:18:38 网站建设 项目流程

刚接手存储相关项目那会儿,我最容易犯的错就是把NAND Flash当成普通存储介质来用——按扇区读写、理所当然地认为每一块都能用。直到产品在用户手里跑了三个月,坏块静默增长,图片文件东一块西一块损坏,我才真正意识到,NAND Flash的坏块管理不是可选项,而是整个存储方案的生死线。这篇关于NandFlash存储原理与坏块管理的内容,我想从物理结构讲到上层应用,把那些数据手册里没写透、测试报告里看不出来的坑,一个一个摊开来讲。

这篇文章适合所有正在做嵌入式存储、SSD固件、U盘/存储卡方案、或者只是想在项目里用一颗裸NAND Flash的朋友。我会先讲清楚存储单元和坏块产生的底层原因,再拆解坏块标记、扫描、管理策略,最后给出一份来自真实项目的避坑清单。即便你是零基础,跟着走一遍也会对NAND Flash的内部逻辑建立起完整的认知框架。

1. 从浮栅晶体管开始:NAND Flash是怎么存住数据的

1.1 浮栅晶体管的“电荷囚笼”

NAND Flash的最小存储单元就是一颗特殊的MOSFET,普通MOSFET只有一个控制栅,NAND Flash在控制栅和沟道之间额外塞了一层浮栅(Floating Gate),浮栅被一层高质量的氧化层完全包围,跟周围绝缘。你可以把浮栅想象成一个四周密封的保险柜,电子一旦被放进去,没有外部电压帮忙,就基本跑不出去。

编程(Program)和擦除(Erase)的本质都是让电子穿过这道氧化层进入或离开浮栅。具体机制叫Fowler-Nordheim隧穿效应——在控制栅和衬底之间加一个足够强的电场,氧化层厚度又很薄(通常只有几纳米到十几纳米),电子就有了量子隧穿的机会,穿过绝缘层落进“保险柜”。

读数据的时候,外部给控制栅加一个参考电压,然后看沟道能不能导通。如果浮栅里存了电子,相当于晶体管阈值电压(Vt)抬高,参考电压不足以让沟道导通,读出“0”;如果浮栅里没有电子,阈值电压低,沟道导通,读出“1”。这里有个反直觉的点:NAND Flash擦除后的状态是“1”,编程后的状态是“0”,所以出厂全片擦除状态下读出来全是0xFF。

1.2 SLC到QLC:同样一颗晶体管塞进更多比特

单比特存储(SLC)只需要判断阈值电压高还是低,但一颗晶体管完全可以做成多比特存储。厂家把阈值电压范围切分成多个区间,每个区间对应一个二进制编码:

  • MLC(2bit/cell):4个阈值区间
  • TLC(3bit/cell):8个阈值区间
  • QLC(4bit/cell):16个阈值区间

阈值区间越多,相邻状态之间的电压间隔就越窄。电荷稍微漏一点、温度漂移一点、读取次数多了产生一点干扰,都可能导致单元读出来落在错误的状态区间。所以高比特密度存储单元的P/E寿命急剧下降。我整理了一张对照表:

类型每单元比特数大致P/E寿命典型应用场景
SLC15万~10万次工业控制、军工、高端嵌入式
MLC23000~10000次消费级SSD、普通嵌入式
TLC31000~3000次主流SSD、手机存储
QLC4500~1000次大容量消费级SSD、归档存储

这里要强调一个常见认知误区:同样叫TLC,不同制程、不同厂商的寿命差异巨大。3D TLC普遍比2D TLC耐用,同样的P/E寿命数值在不同温度环境下表现也完全不同。选型时别只看单颗容量和价格,寿命余量一定要根据产品预期生命周期反推。

1.3 页、块、平面的层级关系,以及“擦写不对称”的根源

NAND Flash不是以字节为单位寻址的,它有一套自己的物理层级:

  • 页(Page):读写的最小单位,常见大小4KB、8KB、16KB,外加一部分备用区(Spare Area/OOB),备用区用于存放ECC校验值、坏块标记、逻辑映射信息等元数据。
  • 块(Block):擦除的最小单位,通常由128、256或512个页组成。
  • 平面(Plane):同一Die内的并行单元,多个平面可以同时执行相同操作。
  • Die(Die):一颗裸片,多个Die封装在一起就是常见的NAND Flash芯片。

最折磨人的是“读写按页、擦除按块”这种不对称。原因在物理层面:擦除需要把整个块衬底抬高电压,让所有浮栅里的电子同时隧穿出来,而块内所有单元共享衬底,没法单独擦除某一页。这种设计让存储介质层就像一块只能整体清除的黑板,要修改一个页,得先把整块黑板腾出来再重写。

这个不对称性直接决定了上层必须引入一层“逻辑地址到物理地址映射”,让写入操作可以平移到其他空白页,而不是原地覆盖。不理解这一层,后面看坏块管理、磨损均衡、垃圾回收都会觉得云里雾里。

2. 坏块到底从哪来:出厂缺陷与使用磨损是两码事

2.1 出厂坏块:晶圆制造留下的先天印记

很多初学者以为拿到手的NAND Flash是全部完好的,实际上任何一颗NAND Flash芯片在出厂时就可能带有坏块,这是晶圆制造和封装工艺无法完全避免的。光刻、刻蚀、氧化层生长过程中,任何一粒灰尘、一次工艺波动,都可能在某个单元上留下缺陷。

制造商会把这些块标记出来,规则各厂略有差异,但大方向一致:通常在块0或块1的第一个页(有的也在最后一个页)的备用区特定位置写入非0xFF的数据,表示该块为坏块。这些出厂坏块的数量上限,数据手册里一般会给出,比如“Max Initial Invalid Block: 2%”,意思是出厂坏块比例不超过总量的2%。

出厂坏块本身不可怕,可怕的是你把它当成好块来用。所以NAND Flash驱动初始化的第一步永远是扫描并建立坏块表,这也是后续所有存储逻辑的地基。

2.2 使用中长出来的坏块:P/E循环与干扰机制

使用中产生的坏块叫运行坏块(Grown Bad Block)或使用坏块(Acquired Bad Block),它的来源比出厂坏块复杂得多。

首先是P/E循环磨损。每次编程和擦除,电子都要穿过隧穿氧化层,反复高电场冲击会让氧化层产生陷阱电荷和物理损伤。随着P/E次数增加,氧化层绝缘能力下降,浮栅里的电荷保持能力变弱,最终出现编程无法完成、擦除无法完成、数据保持时间骤降等问题。一旦某个单元的失效特征无法通过ECC纠正,这个块就会被判为坏块。

其次是干扰机制,这也是实际项目里最容易被QOS忽略的部分:

  • 编程干扰(Program Disturb):编程一个页时,同一条字线上其他未选中页的单元也会受到高电压应力,阈值电压可能被轻微抬高。
  • 读干扰(Read Disturb):读取一个页时,同块其他未读取页的单元也会受到读电压应力,误编程的累积效应会让阈值电压缓慢偏移。
  • 数据保持损耗(Data Retention):浮栅里的电荷会随时间缓慢泄漏,温度越高泄漏越快。高温烘烤下的数据保持时间可能缩短到常温下的十分之一甚至更少。

干扰机制最阴险的地方在于它是“温水煮青蛙”——单元不会瞬间坏掉,而是阈值电压一点点漂移,直到某次读取时ECC已经救不回来,块才正式被判死。

2.3 一个经常被误解的问题:坏块和ECC是什么关系

有人会问:既然有ECC纠错,坏块直接让ECC纠正不就行了吗?这是概念混淆。ECC负责纠正数据读取过程中的比特翻转,坏块则是物理层已经不可靠、无法再稳定存储数据的块。ECC可以在一个坏块“萌芽期”充当缓冲区,但如果这个块已经无法完成擦除或编程,ECC给再多的纠错能力也白搭。

理解这个关系对设置ECC纠错阈值很重要。错误比特数一直在安全阈值以内,说明块还能用;一旦错误比特数持续上涨、接近或超过纠错上限,就该主动把这个块降级并搬迁数据了。把ECC当成坏块检测的“早期预警系统”,而不是坏块的“救命稻草”,这个视角要摆正。

3. 出厂坏块识别:标记读取、扫描流程与校验细节

3.1 出厂坏块标记到底存在哪里

NAND Flash的每个Block都有若干个Spare Area,出厂坏块标记一般写在Block 0和Block 1的第一个Page的Spare Area特定字节处,常用规则是:spare area的第1个字节(或第0个字节,取决于厂商)为0xFF表示好块,非0xFF表示坏块。

不同厂商、不同型号的标记位置可能不同,Samsung与Toshiba/铠侠的标记列地址就可能不一样,Micron又有自己的一套。拿到新芯片第一件事,是去数据手册的“Bad Block Management”章节找标记定义,不要凭经验直接写扫描代码。我在项目里见过第三方驱动写死了偏移位置,换了一颗不同厂的Flash后,把好块当成坏块跳过,容量白白少了几个GB。

3.2 第一次上电扫描流程:为什么不能全信出厂标记

出厂标记只能作为初始参考,芯片经过封装、运输、焊接、回流焊高温之后,可能产生新的坏块。所以量产时的第一次坏块扫描,必须在贴片完成后进行一次全片擦除和读写校验,而不是直接把出厂标记搬进坏块表。

一个实用扫描流程通常是这样的:

  1. 逐块发送擦除命令,擦除失败的块直接标记为坏块。
  2. 对每个块的所有页执行写0xFF或固定Pattern,然后读回比对,出现数据不一致且无法重写成功的块标记为坏块。
  3. 再对每块的前几个页执行多次写读循环,比对上一次多了不少错误的块,也要重点关注。
  4. 将出厂标记和实际扫描结果合并,生成最终坏块表并备份。

这里有一个需要注意的细节:擦除和写读扫描会消耗P/E次数,完整扫描一片大容量Flash是很耗时的。量产效率要求高的话,可对整片进行抽样基线检测,但可靠性要求高的工业场景,强烈建议全量扫描。

扫描时用随机Pattern还是固定Pattern?我的建议是同时做0x00和0xFF交替写测,因为全0写测能暴露出编程不足的问题,全1读测能暴露出擦除残留的问题,实际误码往往在交替边界处更容易现形。再配合多温度点校验,比单温度下测一遍可靠得多。

3.3 扫描时的常见误判与处理

扫描阶段最容易踩的坑是“假坏块”。有些块只是暂时性编程失败,比如操作时序波动、供电电压跌落导致编程电压不够,块本身并没有完全损坏。如果不加以区分,多试几次编程或者重新擦除后就能恢复正常。成熟的驱动会设置重试机制:擦除或编程失败后,先重试2~3次,多次失败才把块标为坏块。

另一个问题是坏块表合并顺序。出厂坏块标记在Block 0/1的Spare Area中,如果这两颗块本身就是坏块,它们的标记位置可能不可读。处理方式是如果Block 0/1中有一块是坏块,仍然要读另一块的标记信息作为出厂坏块Bitmap的补充来源。如果两块全是坏块,量产时需要做特殊处理,必要时在量产工具里强制指定已知可用块作为系统块替代。

4. 运行时坏块管理:检测、隔离与FTL层的完整配合

4.1 运行时坏块检测的三条路径

正式运行阶段,坏块检测不能靠定期全盘扫描,成本太高。实际靠的是以下三条路径:

  • 命令失败路径:编程或擦除命令返回失败状态位,直接标记坏块。
  • 读错误路径:读取时,如果某页校验失败但ECC还能纠回来,记录错误地址和错误比特数,持续纠错超过阈值后标记坏块。
  • 定时巡检路径:对长时间未读取的数据块做后台读扫描(Background Read Scan),把“沉睡”的数据块定期唤醒检查。这项对数据保持类故障尤其有效,企业级SSD基本都有后台巡检机制。

我建议在固件里把这三条路径都做成事件日志,坏块标记操作要留痕。这样后期如果用户报障“数据损坏”,你还能回溯出这个块是在哪一天、因为什么事件被标记的。

4.2 坏块表怎么维护才不容易丢

坏块表(Bad Block Table, BBT)是闪存管理的核心元数据,它要是丢了,整片Flash就面临数据找不到的风险。坏块表必须单独预留区域存放,通常放在NAND Flash开头几个块中,而不是跟用户数据混在一起。

我推荐的落盘策略是双副本加序号版本:

副本存储位置更新方式
主BBT系统块区第1份每次变化后写新序号
备份BBT系统块区第2份主BBT写成功后同步

启动时先读两份BBT,比较版本号,取最新且CRC校验通过的那份。如果某份读失败,直接用另一份重建。如果两份都损坏,退回全盘扫描重建。

这里要提醒一个常见坑:每次坏块表更新都把整张表重写到固定块上,会把这个系统落盘块磨损得很快。正确做法是写成“上一版本备份 + 当前版本”,新BBT写在备用块中,写完再更新索引。BBT区域本身要走磨损均衡,不能只在一个块里反复擦写。

4.3 FTL地址映射、磨损均衡与垃圾回收如何协调

FTL(Flash Translation Layer)是三件事的统筹者:逻辑地址到物理地址映射、磨损均衡(Wear Leveling)、垃圾回收(Garbage Collection)。

磨损均衡解决的是“坏块率不均”的问题。同样P/E寿命的块,有的被频繁写,有的长时间闲着,如果只盯着容量用,某些块会先老死。动态磨损均衡会在每次写入分配物理页时选择P/E次数最少的块;静态磨损均衡会把长期不动的数据搬到老块上,把新块腾出来给频繁写入的数据。没有静态均衡,少量热数据会集中消耗少数块的寿命,整片Flash的实际使用寿命会远低于理论值。

垃圾回收则是在后台搬家的过程。因为NAND只能按块擦除,一个块里只要还有一页有效数据,整块就不能擦。GC会把这些有效页搬到别的空闲块,然后整块擦除,重新变成可写块。GC中最怕遇到的情况就是搬家搬到一半碰到坏块:这时已经在规划中的搬迁数据怎么办?正确的做法是先检查新落盘块是否为好块,如果块已坏就立即切换备用块继续搬,不能中断搬家流程。

4.4 ECC与LDPC:坏块之外的最后防线

再好的坏块管理也不可能让每个块永远不坏,ECC是读取路径上的最后一道防线。SLC时代BCH(1,XX)就够用了,到了TLC/QLC时代,基本都转向LDPC(低密度奇偶校验码)。

LDPC的优势在于它支持软判决,可以读取多个参考电压下的比特可靠性信息,把“看起来模棱两可”的比特判断得更准。我用一个生活化类比解释:BCH像是只看一个字写得像不像,LDPC则是把这个字的每一笔都放大看清楚。对于TLC/QLC那种极其微弱的电压差,软信息几乎是救命稻草。

给E CC留多少冗余、纠错上限调多少,直接决定了系统对坏块的容忍度。一般而言,LDPC纠错能力配置到codeword的1%~2%是一个常见起点,具体要按Flash厂商推荐的BER(误码率)要求和产品生命周期来算。把纠错上限设置得过激进,会导致每个块都要等到病入膏肓才被隔离,坏块管理失去提前预警的意义。

5. 实际项目里的避坑清单:从量产到落地的真实经验

5.1 掉电与坏块表的相爱相杀

运行时最危险的时刻是掉电瞬间。如果你正在更新BBT,写到一半突然掉电,坏块表可能永久损坏;如果正在垃圾回收搬家,数据可能处于新老地址各写一半的状态。没有掉电保护策略的系统,每次意外断电都是一次数据完整性赌博。

我自己在项目里常用这几招:

  • 硬件上增加掉电检测电路,在电压跌落到危险范围之前触发中断,让固件在残存电量里完成当前事务。
  • 软件上采用“两阶段提交”:先把新BBT写到备用块,再把索引指针切换到新块,最后再擦掉旧BBT块。
  • 数据写入采用“先写新页,再更新映射表”的顺序,这样即使掉电,旧映射表仍然指向旧数据,不会产生新老数据都不可用的情况。

5.2 OP预留空间:不只是为了性能

OP(Over-Provisioning,预留空间)通常是用户见不到的那部分容量。很多人把它理解为性能工具——预留多了,GC搬运时就有足够的空白块可用,写放大可以降低。但OP对坏块管理的意义同样重要。

当P/E寿命末期坏块越来越多时,如果没有足够的备用好块替换,整盘会断崖式进入只读状态。OP空间越大,系统对坏块的容忍能力就越强。消费级SSD的OP通常在7%左右,企业级会做到15%~28%,其中一部分就专门给坏块替换和GC留缓冲。工业级方案如果可靠性优先,我建议把OP放到15%以上,千万不要为了标称容量好看压缩OP。

5.3 ECC/LDPC阈值怎么设定才合理

ECC阈值不宜固定不变。最坑人的做法是“有错就纠,纠不过来才处理”,这样等于放弃了预测性坏块隔离。

我惯用的策略是设置两级阈值:

  • 软阈值:错误比特数达到ECC能力上限的50%~70%时,就把当前块加入“关注列表”,触发数据搬迁,并把这个块标记为“即将退役”。
  • 硬阈值:错误比特数达到上限且搬迁失败,直接标记为坏块并更新BBT。

这样设定既避免了过早隔离导致容量浪费,又能保证在错误进一步恶化前把数据搬走。阈值要按Flash温度环境动态微调,高温场景下的BER普遍会更高一些。

5.4 量产测试与老化筛选该覆盖哪些项目

量产阶段最能筛掉坏块问题。我在产线上推过一套组合拳,效果显著:

  • 上电后先做全片擦除和固定Pattern全片读写校验,筛掉贴片损伤和早期失效块。
  • 接着做高温烘烤老化(比如85℃、若干小时),再做一次全片读写校验。这一步能让潜在的Data Retention问题提前现形。
  • 最后写入一份带ECC校验的数据到全片,断电重启后再读回比对,模拟用户“存储后断电,过一阵再读”的真实场景。

如果成本和工期允许,做P/E加速老化测试也很有价值:随机写满一轮、校验、再写满两轮、再校验。坏块数量随时间增长的斜率,能直观反映这批Flash晶圆的体质。

量产测试做得越狠,后面运维越省心。特别是那些宣称“工业级”“车规级”的产品,如果没有完整坏块数据支撑,真出了问题,售后成本会远远超过省下来的测试时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询