上一篇《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早就已经不要的数据。
谢谢你读我的文章
支付宝
微信