上一篇《SSD科普三十三》中,我们留下了一个问题。

当我们在操作系统里删除一个文件时,SSD其实并不知道这个文件已经被删除了。因为文件系统更多是在自己的管理结构里,把这片空间标记成“以后可以重新使用”。

而对于SSD来说,那些LBA里的数据可能依然是有效的。

所以操作系统还需要通过TRIM这样的机制,把一个非常重要的信息告诉SSD:

这些LBA里的数据,我以后已经不需要了。

到了NVMe SSD里,对应的机制通常是Dataset Management命令中的Deallocate。

可问题也随之而来。

既然SSD已经知道这些数据不要了,为什么不直接把它们从NAND里删掉?

这就是这一篇要讲的问题。


一、TRIM到底告诉SSD什么?

很多人第一次接触TRIM时,很容易把它理解成:

操作系统让SSD把某些数据删除。

但更准确地说,TRIM传递的不是一个“立即擦除NAND”的命令,而是一条关于数据是否还需要保留的信息。

对于NVMe SSD来说,Host可以通过Dataset Management命令向SSD描述一组LBA范围,其中Deallocate表示:

这些LBA里的数据,Host以后不再需要保存了。

这里有两个地方很重要。

第一,Host告诉SSD的仍然是逻辑地址。

例如:

LBA 1000 ~ LBA 1999,这些数据可以不要了。

Host并不知道这些数据具体放在NAND里的哪个Block、哪个Page。LBA到物理位置之间的关系,依然由FTL负责。

第二,Deallocate并不等于:

现在立刻把对应NAND擦掉。

它真正告诉SSD的是:

这些数据已经没有继续保存的价值。

至于SSD什么时候真正回收这些物理空间,则是另外一件事情。

所以首先要把两个概念分开:

Deallocate解决的是“数据还要不要”。

Erase解决的是“物理空间什么时候真正被擦除”。


二、SSD收到Deallocate以后,会发生什么?

为了方便理解,我们还是沿用前面文章里的4K LBA。

假设现在有这样一组非常简化的映射关系:

LBA 100 → NAND Page A
LBA 101 → NAND Page B
LBA 102 → NAND Page C
LBA 103 → NAND Page D

从FTL的角度来看,这意味着:

如果Host读取LBA 101,SSD需要找到Page B里的数据。

现在,操作系统删除了一个文件,而这个文件恰好占用了LBA 101和LBA 102。

随后,Host告诉SSD:

LBA 101和LBA 102已经可以Deallocate。

这时候最重要的变化并不是:

Page B和Page C里的数据立刻从NAND中消失了。

而是:

FTL已经知道,LBA 101和LBA 102原来的数据以后不需要继续保留。

实际SSD内部究竟怎样记录这种状态,不同实现可能并不一样。

有的实现可能修改映射信息,有的可能配合额外的元数据结构进行管理。因此这里不能简单地说:

“收到Deallocate以后,SSD一定把L2P清空。”

我们真正需要理解的是它的逻辑结果:

这些LBA原来对应的物理数据,已经没有继续保存的必要了。

于是一个很有意思的状态出现了。

Host看来:

数据已经不要了。

FTL看来:

这些物理数据已经没有价值了。

但是NAND看来:

数据其实可能还在那里。


三、既然数据没用了,为什么不马上Erase?

原因其实出在NAND本身。

前面的文章里我们已经讲过很多次:

NAND可以以Page为粒度进行Program,但是Erase的粒度通常是整个Block。

这就产生了一个非常现实的问题。

假设一个Block里面有很多Page:

Page 0:有效
Page 1:有效
Page 2:已经Deallocate
Page 3:有效
Page 4:已经Deallocate
Page 5:有效
……

现在SSD已经知道:

Page 2和Page 4对应的数据不要了。

能不能直接把Page 2和Page 4擦掉?

通常不能。

因为NAND的普通Erase操作并不是以单独Page为单位,而是整个Block一起擦。

如果为了删掉Page 2和Page 4就直接Erase整个Block,那么Page 0、Page 1、Page 3、Page 5这些仍然有效的数据怎么办?

SSD只能先把它们读出来,再搬到其他地方:

读取仍然有效的数据
↓
写入新的物理位置
↓
更新相应的映射关系
↓
Erase整个旧Block

也就是说:

为了立刻删除几个已经没有价值的Page,SSD反而需要先进行一堆Read和Program。

显然没有必要。

所以更合理的方式通常是:

先记住哪些数据已经没有用了,等以后真正需要回收这个Block时,再一起处理。


四、数据虽然还在NAND里,但已经“死了”

这里很容易产生一个误解。

既然NAND里的数据还没有被擦掉,那么它到底算不算被删除?

要看站在哪一层。

从NAND的物理状态来看,数据可能依然存在。

Cell里的阈值电压状态不会因为一条Deallocate命令立刻发生改变。

但对于FTL来说,这些数据已经没有继续保存的意义。

所以从空间管理的角度,我们可以把这类旧物理数据理解成:

Invalid Data。

也就是说:

NAND里可能还有它,但以后GC处理这个Block时,没有必要再为了保存它而搬迁。

这一点其实才是TRIM真正有价值的地方。

因为对于SSD来说,真正昂贵的不是:

“知道某份数据不要了。”

而是:

“数据明明已经不要了,SSD却还以为必须继续替你保存。”

如果SSD不知道某些数据已经被Host删除,那么以后GC碰到这些数据时,仍然可能把它们当成需要保留的数据。

于是会出现一个很有意思的场景:

Host:

我早就删了。

SSD:

你又没告诉我。

结果SSD还在认真地把这些旧数据读出来、搬到新的位置、继续占用新的NAND空间。

TRIM / Deallocate真正解决的,就是这个问题。


五、Invalid Data并不只来自Deallocate

讲到这里,还需要补一个很重要的点。

Invalid Data并不只是删除文件以后产生的。

另外一种非常常见的来源,就是:

Overwrite。

假设:

LBA 100 → Page A

也就是说,LBA 100当前的数据保存在Page A。

后来,Host又向LBA 100写入了一份新的数据。

由于NAND不能像DRAM一样随意在原来的Page A上直接覆盖修改,FTL通常会把新的数据写到另一个空闲位置。

例如:

新数据写入Page B

随后FTL更新映射关系:

LBA 100 → Page B

那么原来的Page A怎么办?

Page A里的数据其实仍然存在。

但是:

LBA 100已经不再指向它了。

对于Host来说,Page A里的那份数据已经是旧版本。

所以Page A也就变成了Invalid。

这就是Overwrite产生Invalid的过程:

LBA 100 → Page A
↓
Host重新写LBA 100
↓
新数据写入Page B
↓
LBA 100 → Page B
↓
Page A变成Invalid

而Deallocate产生Invalid的原因则不同。

它不是因为:

新数据取代了旧数据。

而是因为:

Host连这份数据本身都不要了。

所以两种Invalid的来源其实不一样:

Overwrite:新数据取代旧数据。

Deallocate:Host直接放弃这份数据。

但到了GC这里,两者最终的结果非常相似:

这些旧的物理数据,都没有必要继续搬迁。


六、真正回收空间的是GC

现在我们再来看一个Block。

假设这个Block里一共有100个Page。

刚写满的时候:

100个Page全部有效。

后来Host不断修改和删除数据。

其中一部分旧数据因为Overwrite变成Invalid。

另一部分因为Deallocate变成不再需要的数据。

最后这个Block可能变成:

20个Valid Page
80个Invalid Page

注意。

虽然80个Page已经没有价值了,但是这个Block此时依然不能直接拿来写新数据。

因为这些Page并没有真正被Erase。

所以当SSD需要新的可写空间时,GC就要开始工作了。

大致过程是:

找到一个包含大量Invalid数据的Block
↓
把剩余Valid数据搬走
↓
Erase整个旧Block
↓
Block重新变成Free Block

如果这个Block只剩下20个有效Page,那么GC只需要搬20个Page。

然后整个Block就可以擦掉。

但假设其中另外40个Page对应的文件,其实Host早就删除了,只是SSD不知道。

那么在SSD看来,这个Block可能不是:

20个Valid Page。

而是:

60个需要继续保存的Page。

结果GC的时候,本来只需要搬20个Page,现在却要搬60个。

其中40个Page实际上早就没有任何价值。

这就是为什么:

Host是否把“哪些数据不要了”告诉SSD,会直接影响GC的工作量。


七、Deallocate减少的不只是写入

前面讲Write Path时,我们一直站在Host的角度看写入。

例如Host写入4KB:

Host Write 4KB
↓
SSD最终把数据写进NAND

但SSD内部真正写入NAND的数据量,往往并不只来自Host。

GC、Wear Leveling以及其他后台管理,都可能产生额外的Program。

所以才会出现一个非常重要的指标:

Write Amplification,也就是写放大。

简单理解:

Host可能只写了100GB数据,但NAND内部实际发生的Program可能达到120GB、150GB,甚至更多。

而GC搬迁有效数据,就是这些额外内部写入的重要来源之一。

现在再回来看Deallocate。

如果SSD提前知道:

哪些数据已经没有必要继续保存。

那么GC时需要搬迁的数据就会减少:

Invalid数据增加
↓
GC需要搬迁的Valid数据减少
↓
内部Program减少
↓
Write Amplification降低

但这里还不只是Program。

因为GC想把一个有效Page搬走,通常不是凭空把数据“复制”过去。

SSD还需要先:

从旧位置Read
↓
再Program到新位置

所以一份本来已经没有价值、却被SSD误认为仍然有效的数据,可能带来的并不是一次无意义的操作,而是:

一次额外的NAND Read + 一次额外的NAND Program。

因此,Deallocate减少的不只是写放大。

它还可能减少GC过程中额外的NAND Read、后端带宽占用以及NAND资源消耗。

不过这一篇不再专门引入“读放大”这个概念。

只需要记住:

少搬一个没用的数据,就少做一次不必要的Read,也少做一次不必要的Program。


八、TRIM、GC和Erase其实是三件不同的事情

写到这里,就可以把几个很容易混在一起的概念分开了。

Delete
文件系统认为:这个文件不要了。
但SSD可能还不知道。
↓
TRIM / Deallocate
Host告诉SSD:这些LBA里的数据不用再继续保存。
↓
Invalid
FTL知道这些旧物理数据已经没有继续保存的价值。
↓
GC
SSD在合适的时候选择需要回收的Block,把其中剩余的Valid数据搬走。
↓
Erase
擦除整个旧Block。
↓
Free Block
Block重新变成可以Program的空闲空间。

所以:

TRIM解决的是“哪些数据不用保留”。

GC解决的是“怎么把这些无效数据集中处理掉”。

Erase解决的才是“怎么让NAND Block真正重新变成可写空间”。

它们并不是同一件事情。


九、如果我真的想把数据彻底删掉呢?

讲到这里,可能有人会问:

既然TRIM以后,数据还有可能暂时留在NAND里面,那如果我真的想把数据彻底清掉怎么办?

这就已经不是TRIM解决的问题了。

因为TRIM / Deallocate的目标主要是:

告诉SSD哪些数据以后不用继续保存。

它更关注的是空间管理和性能,而不是安全销毁数据。

如果目标变成:

我希望原来的数据以后无法再被恢复。

那么就属于另一套机制。

例如NVMe中定义了Sanitize操作,根据设备能力,可以支持Block Erase、Overwrite或者Crypto Erase等方式。

其中Crypto Erase的思路比较有意思。

如果SSD中的用户数据本身就是经过加密以后写入NAND,那么只要让原来的加密密钥失效,即使NAND里的密文还存在,也已经无法再按照原来的方式解密。

所以:

Deallocate解决的是“这份数据还要不要保存”。

Sanitize解决的才是“这份数据以后还能不能恢复”。

两者的目的完全不同。

这一部分我们这里只点到为止,以后如果有机会,可以再单独讲。


十、SSD为什么不急着删除数据?

现在再回到标题:

SSD知道数据不要了,为什么还不直接删除?

答案其实已经很清楚了。

因为对于NAND来说:

立即删除,往往不是最划算的选择。

SSD收到Deallocate以后,真正获得的是一个非常有价值的信息:

这部分数据以后不需要再为它付出保存成本了。

但它不需要马上把对应的NAND物理状态擦掉。

它完全可以等。

等这个Block里的Invalid数据越来越多。

等以后GC真正需要回收这个Block。

然后再把剩余的Valid数据一次性搬走,最后Erase整个Block。

所以TRIM真正做的,并不是:

帮SSD立刻删除数据。

而是:

让SSD知道,哪些数据以后可以放心地不管了。

这看起来只是Host多告诉SSD一句话。

但对于FTL来说,这句话却非常重要。

因为从这一刻开始,SSD终于不用再替Host保存那些——Host早就已经不要的数据。


谢谢你读我的文章