FTL为SSD内部控制器的固件,主要包含3个部分,前端支持主机端的SATA NVMe等协议,中端负责闪存块的管理,为适应主机端的需求和闪存的特性实现的一系列映射管理,垃圾回收,磨损平衡等功能,后端负责实现控制闪存的时序和部分ECC校验。以下结合书籍内容,对各块做一个简单介绍。
FTL的存在的主要原因之一是因为闪存有着与硬盘截然不同的特性,大部分FTL的功能都是为了适应闪存的特性而存在。
(1)闪存的块不能覆盖写,需要先擦除再写入。即不能在原地修改。故而写入的一部分数据失效后,不能重新利用,而是必须要把块上有效的数据统一挪到其他地方去之后,将当前的块整个擦除,才能重新使用这块空间,故而涉及到必须要有 垃圾回收 的概念。而这里又涉及到下面的第二个点,即擦写寿命的问题。
(2)闪存块有一定的寿命,不同的闪存结构 SLC,MLC,TLC有着不同的擦写次数的寿命,到达寿命后,擦写会提示失败,我个人理解为是绝缘层老化了。所以又需要做 磨损平衡 来保证最大写入。(p.s. 这里我并不太理解为什么部分块坏了,会对其他的块也产生影响)
(3)闪存块会存在读干扰的问题,可以理解为每次读会对块造成一定的损伤,如果读到一定次数,就可能出现比特翻转。故而需要在读到一定阈值时,把数据从闪存块搬走。擦除后可以理解为损伤又被重置。这涉及到 RD 的处理
(4)闪存存在电荷流失问题,闪存中的电荷在天然情况下会游离绝缘层,故而需要定期的处理,即 针对 DR 的处理
(5)闪存天生就有坏块,同时在使用过程中,随着寿命的增长,也会出现新的坏块,这就涉及到 坏块管理
(6) MLC和TLC类型的闪存存在 lower page corruption的问题,即其他page写入时的掉电,会导致lower page上的数据也一并的丢失。这就涉及到需要做掉电保护。当然这只是掉电保护的一个场景,还有其他场景也需要掉电保护。
映射管理是指将主机端的逻辑地址与闪存块上的地址进行映射。
根据映射颗粒度的不同,可以分为以下3种映射
上述三种映射方式,都会形成一个映射表,如果以表的相对位置代表逻辑地址,对应位置的值即为物理地址。以256GB的SSD为例,如果每个逻辑页为4KB,则有64M个逻辑页,即64M条映射记录,每条记录代表的物理地址有4个字节,就是需要256MB的映射表。
映射表的设计实现,常见的存在带DRAM和DRAM-Less两种设计。带DRAM,即映射表整个存在DRAM中,查询映射表后就可以访问闪存(但其实也是需要保存到flash的)。DRAM-Less的设计,则必然会有映射表是存储在Flash中的,一种可能的设计提高效率的方式是 采用二级映射,一级映射将闪存分成段,存储在SRAM中,二级映射仅缓存部分内容到SRAM中,只有在访问的逻辑地址的映射记录不在SRAM中时,需要加载读取FLASH中的完整映射表。
还有一种设计是把映射表存储在主机的内存中,NVMe1.2及后续协议及支持该点,称为HMB,host manage buffer。
针对用户的读操作,根据映射表查找到对应的物理地址,将数据从物理地址上读出来即可。
针对用户的写操作,如果是该逻辑地址的首次写,则寻找到空闲的物理地址,写入数据后增加映射关系即可。如果是该逻辑地址的修改写,因为不能覆盖写,则在找到原先的映射关系后,寻找到新的空闲块,重新调整映射关系,并将原来的物理块标记为垃圾数据。
映射表在SSD掉电前,需要写入到闪存中。下次上电时,需要从闪存加载到缓存。
每次写入缓存中的映射表都会增加新的映射关系。
写入区分时机和策略
垃圾回收中比较重要的概念是WA (write amplifier写放大)和 OP(Over provisioning预留空间)
SSD的容量除了会有一部分用于映射表以外,还有一部分容量用于OP空间。OP空间是用户在写满标称的SSD容量后,删除文件后覆盖写,会写入的空间。在不考虑后续介绍的TRIM的情况下,覆盖写操作只能是将原来的映射地址置为失效,将映射地址修改为当前新的映射地址。
如果没有OP空间,把除了映射表以外的所有SSD空间都透给用户,在空间真正用满的时候。因为SSD只能以块为单位来擦除,所以如果不是一个块上的所有页都已经是无效页,这个块是不能被擦除的。就没有地方可以作为缓冲,因为当前的块还不能被擦除,但是又新需要写入新的页,因为已经没有可用块,就无法再写入了,所以OP空间是必须要存在的,但是存在多大的OP空间,又是一个设计上需要考虑的点。
一般而言,写满整个SSD的流程如下:
首先SSD正常执行写入,写入时会把逻辑页写入到不同通道的DIE上,增加底层的并行性,提升写入性能。
如果考虑极端情况只写入不擦除,那么用户就会写满整个SSD容量空间。
写满之后用户删除部分文件,然后继续写入。这时就会把数据写入到OP空间,逻辑页的数据更新到新的地址,之前逻辑页地址对应的物理页上的数据变成了垃圾数据。
在写满整个OP空间,剩余空闲块降低到一定阈值的时候,垃圾回收流程就会触发。
垃圾回收的目标是回收仅有垃圾数据的物理页的空间。核心流程是把闪存块上的有效数据读出来,重新写入新的地址,然后把原闪存块擦除,得到新的可用的空白块。
最简单的回收策略,就是挑选垃圾最多的闪存块进行回收,这样需要搬移的有效数据最少。
针对多通道并行写入的场景,每个通道都需要有一个闪存块,故而需要选择 各个DIE上块号一样的闪存块做垃圾回收。
因为GC的存在,在满盘情况下写入数据——》触发GC——》触发额外的数据搬移和擦除——》故而SSD往闪存中写的数据量比 实际用户想要写入SSD的数据更多。
故而引入了写放大的概念,WA,Write Amplification。
写放大 = 写入闪存的数据量 / 用户写的数据量
空盘的写放大为1,如果有实时数据压缩模块,WA会小于1。
OP越大 up 写放大 down 有效数据 down
OP比例 = (闪存空间 - 用户空间) / 用户空间
会影响写放大的因素主要包括 1.OP空间;2.用户写入数据的模式;3.GC策略;4.磨损平衡;5.读干扰;(4,5 均涉及到数据搬移操作,造成写放大,不仅仅是GC)6.主控(压缩策略);7.TRIM(后续介绍)
垃圾回收的实现主要包含以下三个环节,
称为BPA算法。
常见算法1 为挑选有效数据最小的闪存块(贪心算法),为了寻找和判断,就需要维护每个用户闪存块的有效数据量。当新的数据写入块时,有效记录+1,如果逻辑地址曾经写入过,找到之前所在的块,有效数据-1。
常见算法2 除了考虑有效数据量,还考虑闪存块的擦写次数(暗含磨损平衡算法)给擦写次数和有效数据设定权重因子,缺点是可能导致进一步的写放大。
存在两种方式,只读有效数据或者是全部读取。
方法1,只读有效数据:这就需要知道哪些数据是有效的,固件需要维护一个bitmap表,标识闪存块中哪个物理页是有效的。后续在写入逻辑页时,要把原来闪存块上的数据有效位清零。
方法2,读取全部数据:这样可以不维护一个有效位bitmap表。但是全部读出来之后,还是需要知道哪些数据是有效的,是需要重写的。否则全部写到其他的块上,起不到回收垃圾的作用。判断数据是否有效可以通过读取元数据来实现。
元数据是 SSD在把用户数据写入到闪存时,会额外打包的元数据,包括对应的逻辑地址,数据长度,时间戳。
物理地址 | 元数据 | 用户数据 |
---|---|---|
Pa1 | LogicalAddr1, TimeStamp1 | UserData1 |
Pa2 | LogicalAddr2, TimeStamp2 | UserData2 |
在读取数据时——》读取元数据,获取LPA——》查找映射表——》得到逻辑地址对应的当前物理地址PPA——》判断与当前闪存块的物理地址是否一致,不一致则无效。
通过该种方式判断页是否有效的缺点是不管数据是否有效,都需要读出来才能确认,而且还需要读映射表,开销较大。好处是实现简单,不需要维护额外的闪存块有效页bitmap。
方法3,折衷方法,在闪存块的某个地方单独维护一个 P2L表,即physical to logical 表。在准备回收这个块时,就加载这个存储的P2L表,这样就可以快速得到这个块的P2L,即块上任意物理地址对应的LPA。再通过LPA查找映射表判断数据页的有效性,后段流程和上述方法2的后段流程一致。主要是省去了需要读取整个块上所有页,读取其元数据获取LBA的过程。
该流程即是将有效数据按序写入到新的闪存块中。
何时触发做垃圾回收呢?。有三种常见的策略。
策略1:可用块小于一定阈值,执行Foreground GC前台垃圾回收。这个是属于被动触发。
策略2:Background GC后台垃圾回收,空闲时主动做GC。
策略3:host GC,支持主机控制SSD做GC。
HMS (Host managed SSD)让SSD在主机空闲时才做垃圾回收,避免因为SSD内部的后台任务的执行,影响响应主机端服务的性能。因为SSD内部有后台任务,包括垃圾回收,记录运行日志等等,如果在这些后台任务执行时,host发送请求,就会影响SSD的时延表现。
映射管理和垃圾回收个人理解为FTL的最核心的业务,实现了FTL最基本的功能。FTL还有其他很多功能,包括 TRIM,磨损平衡,坏块管理,RD & DR,SLC cache,掉电恢复。
个人理解主要就是围绕flash的特性和上述映射管理和垃圾回收的流程,进行的性能优化,寿命优化,以及围绕flash特性的异常处理。这些我们在另一篇读书笔记中做介绍。
《深入浅出SSD》