2. NAND Flash 物理特性:从存储单元到坏块管理
做存储驱动这些年,我最大的感触就是:不懂 NAND 物理特性,写出来的驱动就是空中楼阁。你想想看,连存储单元怎么存数据、Page 和 Block 什么关系、擦除为什么那么慢都不清楚,怎么去优化读写性能?
这一章,我们就从最底层开始,把 NAND Flash 的物理特性彻底讲透。
2.1 存储单元结构:SLC / MLC / TLC / QLC
NAND Flash 的存储单元,本质上是一个浮栅晶体管(Floating Gate Transistor)。通过控制浮栅中电子的数量,来代表不同的数据状态。
根据每个单元能存储的比特数,分为四种类型:
| 类型 | 每单元比特数 | 电压阈值状态数 | 典型寿命(P/E Cycles) | 典型应用 |
|---|---|---|---|---|
| SLC | 1 bit | 2 种 | 50,000 ~ 100,000 | 企业级 SSD、工业级存储 |
| MLC | 2 bits | 4 种 | 3,000 ~ 10,000 | 消费级 SSD、高端 U 盘 |
| TLC | 3 bits | 8 种 | 1,000 ~ 3,000 | 主流消费级 SSD |
| QLC | 4 bits | 16 种 | 500 ~ 1,000 | 大容量数据盘、归档存储 |
这里有个关键点:电压阈值状态越多,读取时判断越困难,出错概率也越高。我在项目中遇到过,某款 QLC 颗粒刚出厂时 Raw Bit Error Rate(RBER)就高达 10-3,这意味着每 1000 个 bit 就有 1 个可能出错。如果不配合强大的 ECC 引擎,根本没法用。
核心结论:从 SLC 到 QLC,容量翻倍,但寿命、性能和可靠性都在下降。驱动开发时,必须根据颗粒类型调整 ECC 强度、读写时序和磨损均衡策略。
2.2 Page 与 Block:NAND 的基本操作单位
NAND Flash 的物理结构是分层的:Die → Plane → Block → Page。我们最常打交道的是 Page 和 Block。
- Page:读写操作的最小单位。典型大小是 16KB + 2KB(主数据区 + OOB/Spare 区)。
- Block:擦除操作的最小单位。一个 Block 包含 128 ~ 1024 个 Page,典型大小是 2MB ~ 16MB。
为什么读写和擦除的单位不一样?这跟 NAND 的物理结构有关。擦除需要给整个 Block 的衬底加高压,把电子从浮栅中“吸”出来。这个过程没法只针对单个 Page 做。
我个人的习惯:在驱动初始化时,一定要从 ONFI 或 JEDEC 参数表中读取 Page 和 Block 的大小,不要硬编码。不同厂商、不同制程的颗粒,这些参数可能完全不同。
2.3 读写擦除的基本操作
NAND Flash 的三种基本操作,各有各的脾气。
2.3.1 读操作(Read)
读操作相对最快,典型延迟在 50 ~ 100 μs。流程如下:
- 发送读命令(00h)和地址(5 个周期)
- 等待 tR(读忙时间)
- 读取数据(Page 大小)
这里有个坑:读操作也会干扰相邻单元,这叫 Read Disturb。频繁读同一个 Block 的某些 Page,可能导致其他 Page 的数据出错。我曾经遇到过,一个日志系统频繁读同一个 Block,结果一个月后那个 Block 的 RBER 飙升了 10 倍。
2.3.2 写操作(Program)
写操作比读慢得多,典型延迟在 200 ~ 500 μs。流程:
- 发送写命令(80h)和地址
- 写入数据(Page 大小)
- 发送确认命令(10h)
- 等待 tPROG(写忙时间)
写操作有个硬性要求:只能从擦除状态(全 1)往已编程状态(0)写。也就是说,要改写一个 Page,必须先擦除整个 Block。这就是为什么 NAND 需要 FTL(Flash Translation Layer)来做垃圾回收和磨损均衡。
2.3.3 擦除操作(Erase)
擦除是最慢的操作,典型延迟在 2 ~ 10 ms。流程:
- 发送擦除命令(60h)和 Block 地址
- 发送确认命令(D0h)
- 等待 tBERS(擦除忙时间)
注意:擦除操作会磨损 Block。每个 Block 都有擦除次数上限(P/E Cycles)。超过上限后,Block 的绝缘层会损坏,导致无法正常存储数据。这就是 Bad Block 产生的主要原因。
2.4 Bad Block 管理
Bad Block 是 NAND Flash 的“原罪”。出厂时就有坏块,使用过程中还会不断产生新的坏块。
2.4.1 坏块的分类
- 出厂坏块(Initial Bad Block):芯片出厂时就存在的坏块。厂商会在 Block 的第一个 Page 的 OOB 区标记为 0x00(非 0xFF)。
- 使用中坏块(Grown Bad Block):在使用过程中,由于擦写磨损、读干扰、数据保持等问题产生的坏块。
2.4.2 坏块管理策略
我做过几个项目,总结下来,坏块管理有几种常见策略:
| 策略 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 跳过坏块 | 在逻辑地址映射时,直接跳过坏块 | 实现简单 | 地址映射表复杂 |
| 保留块替换 | 预留一部分好块,坏块出现时替换 | 地址映射稳定 | 需要预留空间 |
| 动态重映射 | 运行时检测坏块,动态调整映射 | 灵活性高 | 实现复杂,有性能开销 |
避坑指南:我曾经在某个项目中,为了省事,直接用跳过坏块策略。结果坏块分布不均匀,导致某些逻辑地址范围性能极差。后来改成保留块替换 + 动态重映射的混合策略,才解决问题。
2.5 本章知识结构图
下面这张图,把 NAND Flash 的物理特性、操作流程和坏块管理串起来了:
2.6 小结
这一章我们聊了 NAND Flash 的物理特性,从存储单元到坏块管理。说白了,这些东西是驱动开发的“地基”。
你想想看:
- 知道颗粒是 TLC 还是 QLC,你才能决定用多强的 ECC
- 知道 Page 和 Block 的大小,你才能设计合理的缓存和垃圾回收策略
- 知道坏块怎么产生、怎么管理,你才能保证数据可靠性
嗯,这些知识点,后面每一章都会用到。尤其是 FTL 设计、磨损均衡和垃圾回收,全都建立在这个基础上。