本文章内容来源《深入浅出SSD 固态存储核心技术 原理与实战.pdf》
由于闪存需要先擦除后才能写入,由于闪存块不能覆盖写,当写人一笔新的数据时,不能直接在老地方更改(闪存不允许在一个闪存页(Page)上重复写人,一次擦除只能写人一次),必须写到一个新的位置,因此,FW (FirmWare,固件)需要维护一张逻辑地址到物理地址的映射表。另外,往一个新的位置写人数据,会导致老位置上的数据无效化,这些数据就变为了垃圾数据。垃圾数据会占用闪存空间,当闪存可用空间不够时,FTL需要做垃圾回收,即把若干个闪存块上的有效数据搬出,写到某个新的闪存块,然后把这些之前的闪存块擦除,得到可用的闪存块,这就是GC (Garbage Collection,垃圾回收),是FTL需要做的一件重要的事情;
note:垃圾回收机制会增加写放大(Write Amplification);
OP:Over Provisioning,预留空间;
我们假设该SSD底层有4个通道(CH0 ~ CH3 ),连接着4个Die(每个通道上的Die可并行操作),假设每个Die只有6个闪存块,所以一共24个闪存块。每个闪存块内有9个小方块,每个小方块的大小和逻辑页大小一样。24个闪存块中,我们假设其中的20个闪存块大小为SSD容量,就是主机端看到的SSD大小;另外4个闪存块是超出SSD容量的预留空间,我们称之为OP,如图4-14所示。
好,一个 SSD摆在我们面前,下面开始写人了。
我们顺序写人4个逻辑页,分别写到不同通道的Die上,这样写的目的是增加底层的并行性,提升写人性能,如图4-15所示。
我们继续顺序写人,固件则把数据交错写人到各个Die上,直到写满整个SSD空间(主机端看到的)如图4-16所示。
整个盘写满了(从用户角度来看也就是整个用户空间写满了,但在闪存空间,由于OP的存在,并没有写满)。那如果想写人更多,应该怎么办?别无他法,只能把看过的内容割爱删除了,腾出空间放新的内容。
下面我们继续拷人。
假设还是从逻辑页1开始写人。这时,SSD会把新写人的逻辑页写人到所谓的OP空间。对SSD来说,不存在什么用户空间和OP空间,它只会看到闪存空间。主机端来数据,SSD就往闪存空间写。图4-17中出现了深色方块,怎么回事?因为逻辑页14的数据已更新,写到新的地方,那么之前那个位置上的逻辑页14数据就失效了,过期了,变垃圾了。用户更新数据,由于闪存不能在原位置覆盖写,固件只能另找闪存空间写人新的数据,因此导致原闪存空间数据过期,形成垃圾。
继续顺序写人,深色方块越来越多(垃圾数据越来越多)。所有闪存空间都写满后,小SSD就是下面这个样子(见图4-18)
等所有Die上的Block 5写满后,所有Die上的Block 0也全部变色了(这些数据都是垃圾)。
现在不仅整个用户空间都写满,整个闪存空间也都满了。如果用户想继续写人后续的逻辑页(36之后的),该怎么办呢?
这时,就需要垃圾回收了。我们暂时从之前的SSD系统中走出来,看看什么是垃圾回收
垃圾回收,就是把某个闪存块上的有效数据(图4-19中浅色方块)读出来,重写,然后把该闪存块擦除,就得到新的可用闪存块了。
图4-19中,Block x上面有效数据为A, B, C, Blocky上面有效数据为D, E, F, G,其余方块为无效数据。垃圾回收机制就是先找一个可用Block z,然后把Block x和Block y的有效数据搬移到Block z上面去,这样Block x和Block y上面就没有任何有效数据,可以擦除变成两个可用的闪存块,如图4-20所示。
再回到我们的小小SSD系统中来。
上例中,由于我们是顺序写人,垃圾集中在Block 0上,上面没有任何有效数据,我们把它们擦除就可以腾出新的写人空间,用户就可以把新的数据写人到垃圾回收完成的Block0上了。从这个例子中我们可以看出:顺序写,即使是闪存空间写满后的写(Full Drive写),性能也是比较好的,因为垃圾回收可以很快完成(也许只需要一个擦除动作)。
但现实是残酷的:用户写人数据,更多的可能是随机写人数据。下面是一个闪存空间经历随机写满后的样子(见图4-21)。
用户如果继续往SSD上写人数据,那么SSD怎么处理?当然需要做垃圾回收。不过,SSD内部状况比之前看到的复杂多了,垃圾数据分散在每个闪存块上,而不是集中在某几个闪存块上。这个时候,如何挑选需要回收的闪存块呢?答案显而易见,挑垃圾比较多的闪存块来回收,因为有效数据少,要搬移的数据少,这样腾出空闪存块的速度快。
对上面每个闪存块的垃圾数(深色方块)做个统计,如表4-2所示。
由于是同时往4个通道上写,我们需要每个通道都有一个空闲的闪存块,因此,我们做垃圾回收时,不是回收某个闪存块,而是所有通道上都要挑一个。一般选择每个Die上块号一样的所有闪存块做垃圾回收。上例中,Block 0上的垃圾数量最多(24个深色方块,最多),因此我们挑Block 0作为垃圾回收的闪存块(这里忽略PE Count等因素,只看垃圾数)。回收完毕,我们把之前Block 0上面的有效数据(浅色方块)重新写回到这些闪存块(这里,我们假设回收的有效数据和用户数据写在同一个闪存块,实际上,它们可能是分开写的),如图4-22所示。
这时,有了空闲的空间(白色方块),用户就可以继续写人数据了。
SSD性能测试,降速说明:SSD越写越慢。没错,其实这是有科学依据的:可用闪存空间富裕时,SSD是无须做GC的,因为总有空闲的空间可写。SSD使用早期,由于没有触发GC,无须额外的读写,所以速度很快。慢慢地会发现SSD变慢了,主要是因为SSD需要做GC;
另外,从上面的例子来看,如果用户顺序写的话,垃圾比较集中,利于SSD做垃圾回收;如果用户是随机写的话,垃圾产生比较分散,SSD做垃圾回收相对来说就更慢,所以性能没有前者好。因此,SSD的GC性能跟用户写入数据的模式(随机写还是顺序写)也是有关的。
垃圾回收可以简单地分为三步:
1)挑选源闪存块。
2)从源闪存块中找有效数据。
3)把有效数据写人到目标闪存块。
挑选源闪存块
挑选源闪存块,一个常见的算法就是挑选有效数据最小的块,这样需要重写的有效数据就越少,写放大自然最小,回收一个块付出的代价也最小。那么,Die中那么多闪存块,怎么就能一下子找到有效数据最小的那个块呢?
这需要固件在写用户数据时做一些额外的工作,即记录和维护每个用户闪存块的有效数据量。用户每往一个新的块上写人一笔用户数据,该闪存块上的有效数据数就加1。同时还需要找到这笔数据之前所在的块(如果之前该笔数据曾写人过),由于该笔数据写人到新的块,那么在原闪存块上的数据就变无效了,因此原闪存块上的有效数据量应该减1;
还是以前面的小型SSD为例:
当用户没有写人任何数据时,所有闪存块上的有效数据都为0,如表4-3所示。
当往Block 0上写人逻辑页1, 2, 3, 4后,Block 0的有效数据就变成4(见图4-26和表4-4)。
当用户空间写满后,每个闪存块上有效数据如图4-27和表4-5下所示。
覆盖写后,用户空间如图4-25所示。
由于逻辑页1,2,3,4写人到Block 5上,Block 5上的有效数据变成4,所以逻辑页1,2, 3, 4在之前所在的Block 0上变成无效,因此在写人逻辑页1,2,3,4的时候,不仅要更新Block 5上的有效数据为4,还应该把Block 0上的有效数据相应地减少4,如表4-6所示。
由于固件维护了每个闪存块的有效数据量,因此在GC的时候能快速找到有效数据最少的那个块。
挑选有效数据最少的那个块作为源闪存块,这种BPA算法叫作Greedy算法,是绝大多数SSD采用的一种策略。除此之外,还有其他的BPA算法。比如,除了基于闪存块有效数据量,有些SSD在挑选源闪存块时,还把闪存块的擦写次数考虑进去了,这其实暗藏着磨损平衡算法(后面会详细介绍)。挑选闪存块时,一方面,我们希望挑有效数据最少的(快速得到一个新的闪存块);另一方面,我们期望挑选擦写次数最小的(分摊擦写次数到每个闪存块)。如果两者都具备,那最好不过了。但现实是,擦写次数最小的闪存块,有效数据未必最少;有效数据最少的闪存块,擦写次数未必最小。因此,需要给有效数据和擦写次数设定一个权重因子,进而得到一个最优的选择。这种方法的好处是可以把磨损平衡算法做到GC中来,可以不需要额外的磨损平衡算法;缺点是相对单纯只看有效数据策略的GC,由于挑选的闪存块可能有效数据很多,因此写放大变大,GC性能变差。
从源闪存块中找有效数据
第二步就是把数据从源闪存块读出来。这里也是有讲究的,怎么读才是最有效率的?全部读出来还是只读有效数据?有人说,当然只读有效数据更有效率了,毕竟我们只需重写有效数据。我赞同这个观点,但问题来了,一个闪存块有那么多逻辑页数据,如何知道哪些数据是有效,哪些又是无效的呢?如图4-29所示。
当我们挑选Block 0(有效数据最少)来做垃圾回收时,如果只读出有效数据(浅色方块),FW如何知道Block 0上哪些数据是有效的呢?办法总是有的。
前面提到,固件在往一个闪存块上写人逻辑页时,会更新和维护闪存块的有效数据量,因此可以快速挑中源闪存块。更进一步,如果固件不仅仅只更新和维护闪存块的有效数据量,还给闪存块一个Bitmap表,标识哪个物理页(例子中我们假设逻辑页和闪存页大小一样)是否有效,那么在做GC的时候,固件只需根据Bitmap表的信息,把有效数据读出,然后重写即可。具体做法跟前面介绍的类似,即固件把一笔逻辑页写人到某个闪存块时,该闪存块上对应位置的Bit就置成I。一个闪存块上新增一笔有效数据,就意味着该笔数据所在的前一个闪存块上数据变成无效,因此需要把前一个闪存块对应的位置的Bit清0(见图4-30)。
固件往Block 0写人逻辑页。、1,2,3后,Bitmap信息如表4-7所示。
固件写满后,整个用户空间如图4-31所示。
覆盖写后,用户空间Bitmap如表4-8所示。
在写人逻辑页I、2, 3, 4的时候,不仅要更新Block 5上的Bitmap,还应该把Block 0(逻辑页1,2,3,4之前所在的闪存块)上对应的Bits清0,如表4-9所示。
由于有了闪存块上有效数据的Bitmap,在GC读的时候,固件就能准确定位到有效数据并读出。Bitmap存在的好处,就是使GC更有效率,但固件需要付出额外的代价去维护每个闪存块的Bitmap。在我们的例子中,每个闪存块(这里指的是所有 Die上同一个闪存块号组成的闪存块集合)只有36个逻辑页,但在实际情况下,每个闪存块有可能存在一两千个闪存页,每个闪存页可以容纳若干个逻辑页,因此,每个闪存块的Bitmap需要占用数目不小的存储空间。对带DRAM的SSD来说,Bitmap的存储空间可能不是问题,但对没有DRAM的SSD来说,可能就没有那么多的SRAM来存储所有闪存块的Bitmap。对DRAM-Les:的SSD来说,由于SRAM受限,只能在SRAM中加载部分闪存块的Bitmap,因此还需要Bitmap的换人换出(同Map Table),给固件带来不小的开销,实现起来没有想象中的简单。
如果没有每个闪存块的有效数据Bitmap, FW做GC的时候,可以选择把所有数据读上来。但此时还需要解决一个问题,那就是这些数据哪些是有效的呢?也就是哪些数据需要重写呢?
SSD在把用户数据写到闪存的时候,会额外打包一些数据,我们叫它元数据(MetaData),它记录着该笔用户数据的相关信息,比如该笔数据对应的逻辑地址、数据长度,以及时间戳(数据写人到闪存的时间)等。因此,用户数据在闪存中是像图4-33这样存储的。
GC的时候,FW把数据读上来,就获得了该笔数据对应的LBA,要判断该数据是否无效,需要查找映射表,获得该LBA对应的物理地址,如果该地址与该数据在闪存块上的地址一致,就说明是有效的,否则该数据就是无效的。
把源闪存块里的全部数据读出来,这种方式的缺点显而易见:GC做得慢;还有一个折中的办法。就是除了维护UP ( Logical to Physical)的映射表,还维护一张P2L (Physical to Logical)的表。该表记录了每个闪存块写人的LBA,该P2L数据写在该闪存块的某个位置(或单独存储)。当回收该闪存块时,首先把该P2L表加载上来,然后根据上面的LBA,依次查找映射表,决定该数据是否有效,有效的数据会被读出来,然后重新写人。采用该方法,不需要把该闪存块上的所有数据一股脑地读出来,但还是需要查找映射表以决定数据是否有效。因此,该方法在性能上介于前面两种方法之间,在资源和固件开销上也是处于中间的。
当有效数据读出来时,最后一步就是重写,即把读出来的有效数据写人闪存。
**
**
由于GC的存在,就有一个问题,用户要写人一定的数据,SSD为了腾出空间写这些数据,需要额外的做一些数据的搬移,也就是额外的写,最后往往导致SSD往闪存中写人的数据量比实际用户写人SSD的数据量多。因此,SSD中有个重要参数,就是写放大(WA,Write Amplification):
对空盘来说(未触发GC),写放大一般为I,即用户写人多少数据,SSD写人闪存也是多少数据量(这里忽略SSD内部数据的写,如映射表的写人)。在SandForce控制器出来之前,写放大最小值为1。但是由于SandForce控制器内部具有实时数据压缩模块,它能对用户写入的数据进行实时压缩,然后再把它们写人到闪存,因此WA可以做到小于1。举个例子,用户写人8KB数据,经压缩后,数据变为4KB,如果这个时候还没有垃圾回收,那么写放大就只有0.50
来看看GC触发后,WA是怎么算的。以前面的GC为例,我们挑选每个Die上的
Block 0做垃圾回收,如图4-23所示。
一共36个方块,其中有12个有效数据块,我们做完垃圾回收后,需把这12个有效数据块写回,如图4-24所示。
后面还可以写人24个方块的用户数据。因此,为了写这24个方块的用户数据,SSD实际写了12个方块的原有效数据,再加上该24个方块的用户数据,总共写人36个方块数据,按照写放大定义:WA= 36/24=1.5。
写放大越大,意味着额外写人闪存的数据越多,一方面磨损闪存,减少SSD寿命,另一方面,写人这些额外数据会占用底层闪存带宽,影响SSD性能。因此,SSD设计的一个目标是让WA尽量小。减小写放大,可以使用前面提到的压缩办法(主控决定),顺序写也可以减小写放大(垃圾集中,但顺序写可遇不可求,取决于用户’Workload),还有就是增大OP(这个可控)。
增大OP为何能减小写放大?先定义OP比例=(闪存空间一用户空间)/用户空间。
还是以前面的SSD空间为例,SSD容量是180个小方块,当OP是36个小方块时,整个SSD闪存空间为216个小方块,OP比例是36/180= 20%。那么180个小方块的用户数据平均分摊到 216个小方块时,每个小方块的平均有效数据为180/216=0.83,一个闪存块上的有效数据为0.83 x 9=7.5,也就是一个闪存块上面平均有7.5个浅色块和1.5个深色块。为了写1.5个用户数据方块,需要写9个方块的数据(原有7.5个有效数据,加1.5个用户数据),写放大是9/1.5=6
如果整个SSD闪存空间不变,还是216个小方块,调整OP比例至72个小方块(牺牲用户空间,OP比例50%),因此,SSD容量就变成144个小方块。144个小方块的用户数据平均分摊到216个小方块时,每个小方块的平均有效数据为144/216=0.67,一个闪存块上的有效数据为0.67x9^ 6,也就是一个闪存块上面平均有6个浅色块和3个深色块。为了写3个用户数据方块,需要写9个方块的数据(原有6个有效数据,加3个用户数据),写放大是9/3=30
从上可见,OP越大,写放大越小。很好理解,OP越大,每个闪存块有效数据越少,垃圾越多,因此需要重写更少的数据,因此写放大越小。同时,由于GC需要重写的数据越少,SSD满盘写性能也越好。
总结一下:WA越小越好,因为越小意味着对闪存的损耗越小,可以延长闪存使用寿命,从而支持更多的用户数据写人量;OP越大越好,OP越大,意味着写放大越小,也意味着SSD写性能越好。
影响写放大的因素主要有:
1.OP: OP越大,WA越小。
2.用户写人的数据Pattern:如前文所见,如果数据都是顺序写人,GC做的量就少(最好的情况是整个闪存块都是无效数据,只需擦除,无需数据搬移),写放大小。
3.GC策略:在挑选源闪存块的时候,如果不挑选有效数据最少(垃圾数据最多)的块作为源闪存块,就增加写放大;另外,控制后台GC产生空闲闪存块的数量,也能减小写放大。
4.磨损平衡:为平衡每个闪存块的擦除次数,需要数据的搬移。
5.读干扰(Read disturb)和数据保存处理(Data Retention handling):数据搬移增加写放大。
6.主控:带压缩和不带压缩的控制器肯定会影响写放大。
7.Trim:有没有Trim,对写放大影响很大。
FTL一般都三个表:
1.LBA→FDA逻辑地址转物理地址的映射表;
2.VPAM:Vaild Page Bit Map,记录每个物理块上哪个页有有效数据;
3.VPC:Valid Page Cout,记录每个物理块上的有效页个数;
这里的VPAM和VPC就是这节垃圾回收章节的讲解;