研究某个东西时,我习惯于一手抓抽象概念一手找个具体样例,我机器出厂内置一块 Samsung 1T PM981a SSD(970 EVO Plus 的 OEM 版本),这次研究的对象又是 SSD,所以自然就以 Samsung 970 EVO Plus 为样例下手。

官方文档 上我们可以获取到如下有效信息:

Field Detail
接口 PCIe Gen 3.0 x4, NVMe 1.3
NAND 闪存 三星 V-NAND 3-bit MLC
控制器 三星内部控制器
高速缓存 三星 2GB 低功耗 DDR4 SDRAM
TRIM 支持 支持
垃圾回收 自动垃圾回收算法

可以看到此处的 Cache 直接就是 2GB 的 DDR4 SDRAM,说白了就是内存颗粒,和 Flash 的容量比例大约在千分之一,结合 RAM 和 ROM 在计算机系统结构上的特性,我们有理由怀疑在这儿放个 Cache 就是为了缓解 PCIe 等高速总线的速率和 V-NAND Flash 数据读写速率不匹配的问题,换句话说就是个基于局部性原理实现的数据缓冲区。

但是我在 SSD 方面的知识储备相当少,所以这样的直觉显然是不可靠的,经过一番对相关资料的查阅,发现其实秘密就隐含在其余的几个参数中:3-bit MLC、TRIM 和垃圾回收。

(一)DRAM

从下面这张去掉贴纸的渲染图中我们可以很清晰地看到 Samsung 970 EVO Plus 的主控、DRAM Cache 和两颗 V-NAND Flash。

ssd

NAND Flash 由多层下面这样的 Die Chip 堆叠而成,可以看到,每层 Die Chip 有若干个区 Plane,而每个 Plane 又有若干块 Block,每个 Block 则由若干个「横向」Page 构成,Page 再细分就是若干个存储位字段的浮栅晶体管了。这是早期的平面 Die Chip 的结构,后来在 2007 年 Toshiba 首次实现了 3D NAND 结构,但是第一个将其商业化大规模生产的却是 Samsung,且 Samsung 称之为 V-NAND(Vertical NAND)—— 也就是这块 970 EVO 所采用的技术。

layout

与上图所示的平面结构相比,V-NAND 在原有的 Page 基础上实现了垂直方向上的延伸,也就是对 Page 的维度进行了扩展,而这相比原有一维的横向 Page 扩展出了一个二维平面。你可以理解为,就像楼房与平房,V-NAND 的 Block 就是在原有平面 NAND 的基础上拔高了一部分 —— 同时我们依然称之为 Block。Block 的维度只是在我们理解其寻址方式时会有影响,所以下文为简单起见一般用二维 NAND 进行解析。

SSD 中使用地址解码器(Address decoder)来通过电路获取单位 Page 里浮栅晶体管所存储的位字段信息,即用特定参数的激励激活对应 Page 里的浮栅晶体管,而后通过与浮栅晶体管直接连接的上行电路获取及改变它所存储的电学特性数据,也就是位。如果是 V-NAND,那我们还需要在地址解码器(Address decoder)的基础上加上一个位线选择器(Bitline selectors)来保证 Page 编码的唯一性。举个不太恰当的例子,位线选择器就相当于坐标中新加上的一个维度。

一般来说,一个不到指甲盖大小的 NAND Flash 颗粒的容量在数十 MB 到几 TB 不等,你可以想见里面有多少个浮栅晶体管,里面给如此数量的浮栅晶体管作基础设施的供电、数据传输等电路结构又是什么规模,而这还只是一个功能比较单一的存储芯片,处理器芯片实现功能的复杂程度要远远大于此类存储芯片,所以……

对如此复杂且庞大的几何结构来说,一个索引是必要的基础设施,换句话说,增删改查这类高频次文件读写操作的背后必须要有一个描述「逻辑页到物理页映射」的表(Map table)支持。实际上,当 CPU 给出的数据通过直连 PCIe 或者 PCH 转接的 PCIe 通道送至 SSD 接口时,会通过主控写入 NAND Flash,这个过程中就会用到该表。用户每写入一个逻辑页就需要更改一次 Map table;当读取一个逻辑页时,SSD 则会查找 Map table 中该逻辑页对应的物理页,然后再访问 Flash 读取所要的数据。

一般映射的逻辑页的大小为 4KB,物理地址的大小为 4Byte,那么一个 256GB 的 SSD 的映射表大小为:256GB÷4KB×4B=256MB256GB÷4KB×4B = 256MB,也就是说映射表的大小大约是 SSD 容量的 1/10241/1024—— 这基本就是 DRAM 的大小。

没错,DRAM 其中一个重要作用就是存储 FTL Map table。但是,DRAM 掉电数据是会丢失的,那映射表怎么办?答案是在 SSD 掉电之前,它会把映射表写入到 Flash 中去,下次上电初始化时再重新把映射表读出来放在 DRAM 中。

DRAM 另一个作用是缓解写放大(Write Amplification)。

结构决定性质,SSD 中的这种微观几何结构就决定了它是以 Page 为单位来进行数据读写的,但 NAND Flash 又有个特殊之处,就是它在写入内容之后不能直接覆写,必须先擦除再覆写,而这个过程很慢。而且,擦除操作由于电路特性的影响最小单位是 Block,也就是说目前覆写只有一种方法就是先将整个 Block 的内容复制一份放到 DRAM 或者 Over Provisioning 甚至其他空闲的 Block 中,再擦除该 Block,然后一起写入该 Block 的原有内容和需要更新的 Page 内容。

比如说,某 SLC SSD 其中一个 Block 中有 256 个 Page,每个 Page 可存放 4KB 数据,而所有 Page 中都已经存有数据,且都没存满,我们现在要多存入 4KB,要怎么做呢?做法就是首先将该 Block 中的所有数据复制一份,进行整合,再擦除,最后把整合好的内容合并写入该 Block 中,结果就是我们为了写入 4KB 的有效数据而造成了 1MB 的实际数据写入。这是个简化的写放大模型,实际导致写放大的因素还有很多。

缓解这种写放大现象的其中一个办法就是把数据先放在 DRAM 中,等攒够一个 Page 容量时再写入到 NAND Flash 里。而且,如果某些 Block 中的 Page 里有部分已经被标记删除的无效数据,我们也需要将其进行整合,也就是垃圾回收(Garbage Collection),这个时候 DRAM 也可以作为缓冲区。

(二)SLC Cache

上文中的 SSD Cache 我们默认是 DRAM,但实际上如果不指明语境的话,SSD Cache 还有另一种含义:SLC Cache。

我们已经了解了 SSD NAND Flash 的基本结构,知道它使用浮栅晶体管来存储位字段,但我们却没具体指明一个浮栅晶体管可以存储几个位。事实上,浮栅晶体管可以粗浅地理解为一个电子的容器,SLC(Single-Level Cell),意味着每个浮栅晶体管只存放 1 位数据,靠电子状态的有或无来输出成数据,也就是最简单的 0 和 1;MLC(Multi-Level Cell),则每个存储单元可存放 2 位数据,电子量会分为高、中、低与无四种状态,转换为二进制后变成 00、01、10、11;TLC(Triple-Level Cell) ,更进一步将电子状态分成 8 种,换算成二进制的 000、001、010、011、100、101、110、111,也就是 3 位数据;新的 QLC 已经有了 16 种状态,存放 4 位数据。

我们初学二进制的时候就体会到了仅仅规定两种状态的好处,此处显然 SLC 是读写速度最快、最不易出错的一种方式,同时也是对浮栅晶体管利用效率最低的一种方式,所以也最贵,消费级 SSD 很少有 SLC 的产品,大部分都是企业级 SSD 才会使用。

而事实上,浮栅晶体管的物理结构都是类似的,具体的 SLC、MLC、TLC 等只是主控读写策略的不同,是我们人为规定的几种状态,换句话说,对某个具体的浮栅晶体管而言,它究竟是 SLC 还是 MLC 是主控说了算。那么,我们完全可以通过调整主控的写入策略来将一部分空闲的 Block 作为 SLC 来用,实现高速写入,待写入完毕后再将这部分缓存的 SLC 数据改写到 MLC 数据区来实际存储。只是问题在于,如果我们要写入单位大小的 MLC 数据,那么就需要动用 3 倍数量的浮栅晶体管来作为 SLC,而 SSD 中的空闲空间是有限的。所以,SLC Cache 也不是万能的,它要求 SSD 中必须有一定的空闲容量。

平时我们说 SSD 或者手机最好不要把容量用得太满,否则会大幅影响性能,其实道理就是如此。

如果你对 SSD 的知识储备也和我一样不是很多,那我猜这个时候你会有一个疑问,那就是如果我们异常断电的话,上文中提到的映射表岂不是会直接丢掉?确实是的,所以工程师设计了一些机制来预防这种情况(开头提到的大电容供电就是其中一种),也设计了另外一些机制来修复丢失 FTL 映射表的问题。

早年间,有个很流行的梗叫「30 分钟大法」,意思是说如果 SSD 异常断电后发生掉盘(操作系统甚至 BIOS 不识别),那么可以尝试什么也不做,上电静等至少 30 分钟,再重启电脑,SSD 就有可能会自己恢复正常。实际上异常断电后的掉盘就是因为 DRAM 中的 FTL 映射表丢失导致主控不响应主板信号,但是成熟的 SSD 厂商都会在主控固件中内置 FTL 映射表重建的功能,当其丢失时,主控就会自动开始逐页扫描 NAND Flash,修复 Flash 中上个版本的 FTL 映射表。这个过程有长有短,具体要看厂商的固件性能和颗粒容量等,一般在几分钟到几十分钟不等。所以,SSD 异常断电掉盘后「30 分钟大法」或许就可以起效果。

(三)FTL

其实,可以发现,SSD 之所以能够如此强大,我们之所以能够驯服特性这么奇怪的 NAND Flash 颗粒作为存储器,主控功不可没。

主控要实现诸如 Mapping、Buffer Management、GC、WL、RAID on Chip 等等的功能,而这些功能的具体实现又要依赖复杂的软件层 Flash Translation Layer,即 FTL。

由于 FTL 是 SSD 设计厂商最为重要的核心技术,因此,没有一家厂商愿意透露这方面的技术信息,并且也一直没有业内的技术规范、标准存在。现在能生产 NAND 芯片的厂家在 Wikipedia 上能查出 11 家,而能生产主控芯片和固件的就 4 家。他们各自的技术核心都在 FTL 中的各种调优上,再加上 TRIM 等等特性,这也就决定了 SSD 硬盘性能的好坏。

关于 FTL 的实现,我找到一篇论文,说实话看不多懂,但是侧面也能看出 FTL 的宏大与精妙。

A superblock-based flash translation layer for NAND flash memory