Flash芯片在写入数据的时候有诸多效率低下的地方。包括现在常用的U盘以及SSD中的Flash芯片,或者Bios常用的EEPROM,它们都不可避免。
1. Flash芯片存储的通病之一:Erase Before Overwrite
对于机械磁盘来说,磁盘可以直接用磁头将对应的区域磁化成任何信号,如果之前保存的数据是1,新数据还是1,则磁头对1磁化,结果还是1;如果新数据是0,则磁头对1磁化,结果就变成了0。而Flash则不然,如果要向某个Block写入数据,则不管原来Block中是1还是0,新写入的数据是1还是0,必须先Erase整个Block为全1,然后才能向Block中写入新数据。这种额外的Erase操作大大增加了覆盖写的开销。
更难办的是,如果仅仅需要更改某个Block中的某个Page,那么此时就需要Erase整个Block,然后再写入这个Page。那么这个Block中除这个Page之外的其他Page中的数据在Erase之后岂不是都变成1了么?是的,所以,在Erase之前,需要将全部Block中的数据读入SSD的RAM Buffer,然后Erase整个Block,再将待写入的新Page中的数据在RAM中覆盖到Block中对应的Page,然后将整个更新后的Block写入Flash芯片中。可以看到,这种机制更加大了写开销,形成了大规模的写惩罚。这也是为何SSD的缓存通常很大的原因。
就像CDRW光盘一样,如果你只需要更改其上的几KB数据,那么就要先复制出全盘700MB的数据,然后擦除所有700MB,然后再写入更改了几KB数据的700MB数据。
SSD的这种写惩罚被称为Write Amplification(写扩大),我们依然使用写惩罚这个词。写惩罚有不同的惩罚倍数,比如,需要修改一个512KB的Block中的一个4KB的Page,此时的写惩罚倍数=512KB/4KB=128。小块随机写IO会产生大倍数的写惩罚。
当SSD当向Flash中的Free Space中写入数据时,并没有写惩罚,因为Free Space自从上次被整盘Erase后是没有发生任何写入动作的。这里又牵渗到一个比较有趣的问题,即存储介质如何知道哪里是Free Space,哪里是Occupied Space呢?本书中多个地方论述过这一点。只有文件系统知道存储介质中哪些数据是没用的,而哪些正在被文件系统所占用,这是绝对无可置疑的,除非文件系统通过某种途径通告存储介质。SSD也不例外,一块刚被全部Erase的SSD,其上所有Block对于文件系统或者SSD本身来讲,都可以认为是Free Space。随着数据不断的写入,SSD会将曾经被写入的块的位置记录下来,记录到一份Bitmap中,每一比特表示Flash中的一个Block。对于文件系统而言,删除文件的过程并不是向这个文件对应的存储介质空间内覆盖写入全0或者1的过程,而只是对元数据的更改,所以只会更改元数据对应的存储介质区域,因此,删除文件的过程并没有为存储介质自身制造Free Space。所以说,对于SSD本身来讲,Free Space只会越来越少,最后导致没有Free Space,导致每个写动作都产生写惩罚,类似Copy On Write,而且Copy和Write的很有可能都是一些在文件系统层已经被删除的数据,做了很多无用功,写性能急剧下降。对于一块使用非常久的SSD来讲,就算它在被挂载到文件系统之后,其上没有检测到任何文件,文件系统层剩余空间为100%,这种情况下,对于SSD本身来讲,Free Space的比例很有可能却是0,也就是说只要曾经用到过多少,那么那个水位线就永远被标记在那里。
思考
令所有人都无法理解的是,为何哪怕在OverWrite一个Cell之前必须将对应的目标——整个Block放电处理呢?其原因在于干扰。由于电路设计原因,一个Page中的多个位,也就是Cell,如果有的正在充电(写入0),有的正在放电(写入1),那么会产生不可忽略的干扰导致问题,正因为这个限制,所以必须取舍,要么就对整Block中的所有Cell充电(写0),要么就对其整体放电(写1)。最后,研发人员下定决心,就取后者吧,每次先预先将整个Block擦成全1,然后再接受IO,IO数据中遇到为1的位,对应的Cell不动作,遇到为0的位,则对应的Cell进行充电。这样,不管任何时候,对于一个Block中的所有Cell来讲,只有“全部在放电”或者“全部/部分在充电”这两个状态,不会产生干扰。
好,那么为何非要一次擦一整个Block呢?一次擦一个Page不行么?比如如果要对某Page写入101010,那么可以先对其全擦1,或者只对其奇数位擦1,结束后再向其偶数位充电,也就是写0?其实这牵扯到两个问题,一个是管理粒度的问题,粒度越小,管理开销就越大,表现在导线与电路开销和能耗方面,可擦除粒度越小,需要的导线和电路就越多;第二则是效率问题,如果没遇到一个Page写,就预先擦除一下再写,这样写惩罚绝对是2倍的关系了,SSD有种算法可以避免写惩罚,见下文。
但是,擦除整个Block,解决了干扰问题的同时却增加了写惩罚,这岂不是得不偿失么?非也。由于SSD使用一种办法来避免写惩罚以及Wear Off,而这种办法的使用,需要大粒度的Erase以便节约时间和提高效率,同时不增加写惩罚。这种办法我们将在下文描述。
EEPROM每次只擦除一Byte,所以性能比Flash要差。
每个Block中的Page必须被按照一个方向写入,比如每个Block为128个Page,共512KB,则当这个Block被擦除之后,SSD控制器可以先向其中写入前32个Page(或者10个Page,数量不限),一段时间之后,可以再向这个Block中追加写入剩余的Page(或者多次追加一定数量的Page写入)而不需要再次擦除这个Block。SSD控制器会记录每个Block中的大段连续空余空间。但是不能够跳跃的追加,比如先写入0~31这32个Page,然后写入64~127这64个Page,中间空出了32个Page没有追加,控制器是不会使用这种方式写的,Page都是连续排布的。但是一般来讲,控制器都是尽量一次写满整个Block的从而可以避免很多额外开销。
2.Flash芯片存储的通病之二:Wear Off
随着FG充放电次数的增多,二氧化硅绝缘层的绝缘能力将遭到损耗,最后逐渐失去绝缘性,无法保证FG中保有足够的电荷。此时,这个Cell就被宣判为损坏,即Wear Off。
思考
SSD是如何判断出这个Cell已经不能保存数据的呢?在Write操作中,会向Cell中充电,成功后会在对应的Bit Line中体现出Cell当前的电势,通过检查这个电势表示的位是否与待写入数据中对应的位一致来判断,一致则判断为完好,不一致则损坏。如果遇到写入数据位为1的情况,那么写入过程中对这个Cell不会有任何动作。如果这个Cell已经损坏,即不能被充电或者充电量太低以至于不能感应出足够的电势,那么当数据被写入之后,依然被判断为完好。这虽然与实际不符,但是不影响使用,一旦这个实际已经损坏的Cell在下次被更改为0,就会被判断出已损坏。
损坏的Cell将拖累这个Cell所在的整个Page被标记为损坏,因为SSD寻址和IO的最小单位为Page。损坏的Page对应的逻辑地址将被重定向映射到其他完好的预留Page,SSD将这些重定向表保存在ROM中,每次加电均被载入RAM以供随时查询。
MLC由于器件复杂,其可擦写的寿命比较低,小于10000次。而SLC则高一些,十倍于MLC,小于100000次。这个值是很惊人的,对于某些场合下,有可能一天就可以废掉一大堆Cell/Page,几个月之内当预留Page都被耗尽后,就会废掉整个SSD。这是绝对不能接受的。
写惩罚会大大加速Wear Off,因为写惩罚做了很多无用功,增加了不必要的擦写,这无疑使本来就很严峻的形势雪上加霜。但是对于读操作,理论上每个Cell可以承受高数量级的次数而不会损耗,所以对于读来说,无须担心。