产生的背景

linux-2.6.27后,内核加入了一种新型的flash文件系统UBI(Unsorted Block Images)。

目前管理FLASH的方法主要有:

1、采用MTD+FTL/NFTL(flash 转换层/nand flash转换层)+ 传统文件系统,如:FAT、ext2等。

     FTL/NFTL的使用就是针对FLASH的特有属性,通过软件的方式来实现日志管理、坏块管理、损益均衡等技术。但实践证明,由于知识产权、效率等各方面因素导致本方案有一定的局限性。

2、采用硬件翻译层+传统文件系统的方案。这种方法被很多存储卡产品采用,如:SD卡、U盘等。

    这种方案对于一些产品来说,成本较高。

3、采用MTD+ FLASH专用文件系统,如JFFS1/2,YAFFS1/2等。它们大大提高了FLASH的管理能力,并被广泛应用。

JFFS2、YAFFS2等专用文件系统也存在着一些技术瓶颈

  1. 内存消耗大

  2. 对FLASH容量、文件系统大小、内容、访问模式等的线性依赖

  3. 损益均衡能力差或过渡损益

  4. YAFFS等皆无法操控大的Nand Flash空间

  在此背景下内核加入了UBI文件系统的支持。

垃圾回收

flash的垃圾回收

        为flash 设计的文件系统要求异地更新(out-of-place update)。这是因为flash 在写之前必须要先擦除,且再次擦除之前只能写一次。如果擦除块(eraseblocks)很小且可以快速擦除,那么可以将它们看作磁盘扇区(disk sector),但是实际上不是那种情况。读出一个整块的擦除块,擦除它,再回写更新的数据,所花时间比单独在其它已经擦除了的擦除块更新数据长100倍。换句话说,对于一个小的更新,在本地更新比异地更新更新时间长100倍。

        异地更新要求垃圾回收(Garbage Collection)。当有数据异地更新时,原来的擦除块就可能同时包含有效数据和废弃数据(这些数据在别的地方已经更新)。这样到最后,文件系统将用完所有空的擦除块,以至于每个擦除块包括有效数据和废弃数据。为了在别的地方写新的数据,​必须有一个擦除块是空的以至于可以擦除和重新使用​。​查找包含许多废弃数据的擦除块,并移动有效数据至其它擦除块,这个过程叫垃圾回收

        ​为了便于垃圾回收,文件系统为所有文件建立树状节点结构​。为了能回收一块擦除块,文件系统必须能确认存储的数据。这是文件系统面对的一般索引问题的矛盾。文件系统通常以一个文件名开始和必须找到属于这个文件的数据。垃圾回收可以开始于任何数据且必须发现这些数据属于哪个文件。解决这个问题的一个方法是根据文件的数据存储元数据(metadata)。数据和元数据结合在一起称为一个节点(node)。每一个节点记录着其属于哪个文件和这个节点包含着什么数据。JFFS2和UBIFS都是按照一个节点结构设计的,节点结构使能它们的垃圾回收器直接读擦除块,决定哪些数据需要移动,哪些要丢弃,根据实际情况去改变索引。

ubi的垃圾回收

ubifs概述_垃圾回收

UBIFS设计简介

        JFFS2和UBIFS之间最大的不同就是UBIFS将索引存储在flash上,而JFFS2存储在内存中,当文件系统被挂载的时候,内存将重新建立索引。JFFS2这样就潜在地给自己最大尺寸做了一个限制,因为挂载时间和内存使用情况都随着flash的大小而线性增长,BUIFS就是被设计用来克服这个限制。

        不幸的是,将索引存储在flash上是非常复杂的,因为索引本身也需要异地更新。当索引的一部分被异地更新,那么与更新的索引相关的其它部分索引也必须要更新。然后,依次地,相关部分的相关部分也必须被更新。这样看起来好像永无止境地更新下去,一个解决的办法就是使用游离树(wandering tree)。

        对于UBIFS,游离树(wandering tree,实际上是一个B+树)只有树上的叶子包含文件信息。他们是文件系统的有效节点。树的内部元素是索引节点(index nodes)并包含相关的子节点。也就是说,一个节点记录着它的子节点的位置。所以UBIFS 的游离树可以看成两个部分。顶部分由创建树结构的索引节点组成,底部分由指向实际数据的叶子节点组成。顶部分可以简单地看作索引index。一个文件系统的更新过程由创建一个新的叶子节点和添加该节点到树上(或者代替原来的树中的节点)组成。之后,其父索引节点必须也要被代替,从而父节点的父节点,一直到树的根(root),都必须要被代替。要被代替的索引节点数等于树的高度。这里留下的问题就是怎么知道tree的根在哪里。在UBIFS中,根索引节点的位置存储在主节点(master node)里。

UBIFS优势

扩展性好

    Data structures are trees

    Only journal has to be replayed

    UBIFS的大小适应Flash的容量,即在mount的时候,内存占用和IO速度并不依据flash的容量,ubifs可以轻松搞定1Gb以上容量的flash。

    但是ubifs所依靠的ubi有扩展性限制。尽管如此,ubifs的堆栈长度还是要比jffs2好。因此如果使用ubifs遇到了瓶颈,那么更多时候需要依靠UBI2,而不是去修改ubifs。

性能好

    Write-back

    Background commit

    Read/write is allowed during commit

    Multi-head journal minimizes amount of GC

    TNC makes look-ups fast

    LPT caches make LEB searches fast

   ​ 快速IO
        
​就算不启动回写功能(挂载的时候使用-sync参数),ubifs还是表现出了很高的性能,非常接近jffs2的表现。要和jffs2比

较io同步速度是很困难的,因为jffs2不需要维护flash上的数据结构,所以它没有维护开销,但是ubifs要啊。可ubifs依然很快,因为他是日志型的文件系统,它并不是真正的把数据物理的从一个地方写到另一个地方,而是添加相应的信息到文件系统索引,为新的日志选择不同的物理块(ubifs的日志在不断改变位置……)。这还有一些技巧,像多头日志,也可以帮助ubifs提高性能。

    ​快速挂载

        不像jffs2那样,ubifs不需要扫描整个flash然后再去挂载。ubifs挂载到设备上只需要消耗毫秒级的时间。并且也不依赖与flash的容量。但是ubi的初始化时间是要根据flash的容量的,而且这时间也是要统计进去的

有断电容错性

    // All updates are out-of-place was extensively tested.

    ubifs是日志型文件系统,它可以容忍突然崩溃和突然重启。UBIFS只需查看日志们就可以从奔溃中恢复过来,但是挂载

的时间可能会稍微慢一点。因为它需要去读日志,但是不需要扫描整个flash。

在线压缩 

   ​在线压缩 (On-the-flight compression):数据会经过压缩再存储到flash上。这可以使得flash上存放更多的数据,

这个跟jffs2很像,ubifs也可以灵活的开启关闭压缩功能。

   目前只支持zlib和LZO压缩格式,但添加其他的也不困难。

数据可靠性高

    All data is protected by CRC32 checksum.Checksum may be disabled for data

可恢复性

    // All nodes have a header which fully describes the node

    // The index may be fully reconstructed from the headers

    如果ubifs的索引信息被破坏,可以完全恢复。ubifs的每一块信息都有一个头信息,来描述这个块的信息。通过扫描flash可以重建这个索引。

    这跟jffs2很像。如果使用fat32文件系统的时候,你已经清除了fat表,这对fatfs是致命的。但是,如果你清除了ubifs的索引,它还可以重建,还有用户层工具可以做这个事情。

ubi和ubifs区别

MTD:提供对各种flash芯片的访问接口。(drivers/mtd)

UBI:类似于LVM的逻辑卷管理层,主要实现损益均衡,逻辑擦除块、卷管理,坏块管理等。工作在MTD上。(drivers/mtd/ubi)

UBIFS:基于UBI的FLASH日志文件系统,工作在UBI之上。(fs/ubifs)。UBIFS与一般的block device不兼容。

ubifs的6个区

        UBIFS文件系统运行在UBI系统之上,它会把UBI volume划分为6个部分。

        UBIFS维护着一棵wandering tree,叶子节点保存着文件信息,它们是文件系统的有效节点。树的内部节点是index node保存着到children的索引。所以wandering tree可以视为两个部分,顶部保存树结构的​索引节点​(index nodes),底部则是真正文件数据的leaf node(​叶子节点​)。

超级块区

        超级块区(superblock area)使用LEB0区块。它在文件系统创建时建立,占用一片LEB存储uperblock node。

        一般来说,superblock node保存文件系统很少变化的参数,例如:索引树扇出(indexing tree fanout)、压缩类型。superblock在mount时被读出。superblock node仅仅占用LEB0的前4096个字节。

        superblock几乎不改变,只有一种情况会导致superblock node被重写,就是自动resize时。之所以需要自动resize,是因为创建ubifs文件系统镜像时,并不知道将要mount的UBI bolume的大小,所以当我们将UBIFS镜像安装到UBI上时,UBI的尺寸可能实际上小于UBIFS镜像所需要的最大空间,此时就需要把UBIFS resize以适合UBI volume。

控制节点区

        控制节点(​master node​)保存了所有在flash中(不在固定逻辑位置)的结构的位置。master node被重复的写到了LEB1和LEB2中(LEBs是UBI抽象出来的)。

        UBI把PEBs映射到LEBs上,所以LEB1和LEB2可以是UBI设备的任意位置,而且UBI一直记录着他们在哪。

        UBI使用两个擦除块来保存两份master node的备份,这是为了数据恢复的,因为有两种情况可能导致master node的破坏或者丢失:

第一种:正在写master node时掉电了。这种情况下,是有可能恢复的,因为之前的master node版本能被使用。

第二种:flash硬件出问题了。这种情况下,是不可能恢复的,因为无法确定哪个master版本是有效的。

        此时,需要用户空间的程序来分析存储设备的所有节点并且尝试恢复或者重新创建损坏的或丢失的master node。

使用两份拷贝使得有可能能判断是上边那种情况发生了并进行处理。

        master node大小为512 bytes,每次写入master node会顺序的使用LEB的空闲page,直到没有空闲page时,再从offset zero开始写master node,这时会重新unmapped LEBs为另一个erased LEB。注意,master node不会同时unmapped两个LEBs,因为这会导致文件系统没有有效master node,如果此时掉电,系统无法找到有效master node。

        为了降低启动时的扫描时间和运行的内存消耗,UBIFS 将这样的树状结构保持在 FLASH 上,而不是在内存中。​master node指向root idex node​,这样就只需要在挂载的时候扫描master area便可以得到所有文件的信息。​  ​     ubifs概述_文件系统_02

日志区间

        日志区间(Journal/log area,或commit area)从LEB3开始,占用空间不确定。

        UBIFS使用日志的目的是为了减少对flash index(main area区的文件的索引,实际索引)的更新频率,因为更新文件时,相应的文件信息和数据会变化,这棵树的节点就要发生变化,整个文件系统的索引节点都要定期更新,这样的话会非常影响效率。因此采用日志区间,当添加叶子节点时,会先将其添加到日志中,只更新内存中的节点,不再提交到flash中,然后再定期提交日志,这样的话,效率会有极大的提高。当需要修改索引树叶节点时并不会马上更新flash上的索引树,首先要更新RAM中的TNC(索引在内存中的copy),同时将更新信息以日志方式记录在flash中(增加新的bud),等到commit时再更新闪存上的索引树。

        日志由log和bud(芽)组成。log记录日志位置,log包含两种类型的节点:commit开始节点(UBIFS_CS_NODE)、引用节点(UBIFS_REF_NODE)。bud:上一次 commit 之后这一次 commit 之前这段时间中,我们操作了哪些 LEB,这些LEB称为bud。commit开始节点记录commit过程的开始,引用节点记录bud的数量。日志有多个head,日志LEBs可以不连续。

journal特点

    所有文件系统的改变都会到journal中;

    索引信息(indexing information)在ram中修改,而不是在flash上;

    进行挂载时,journal被扫描,重现(re-play)

    journal像UBIFS的小JFFS2

    journal的大小存储在superblock,可以被修改。       

journal详细特性:

    在commit之后,会选择不同的LEBs给journal;

    不要把数据移出journal,应该移动journal;journal一直在改变位置。

    journal有多个头;

    journal不需要是连续的:LEBs可能是任意地址、用于journal的LEBs不需要是empty的

TNC树

        TNC(Tree Node Cache)作用:TNC树是日志区在内存中的一个拷贝,它起到缓存index node的作用(加速indexing tree的速度)。内存紧张时TNC占用的内存空间可被缩小。

        TNC树(Tree Node Cache):UBI文件系统日志区是建立在Flash上的,每当文件系统运行时,都会将日志区这个结构映射到内存中,我们称为TNC树(它也是B+树)。当我们要对文件系统节点修改、写数据的时候,先在内存TNC树做标记,等到commit的时候把修改的数据统一写到Flash中。

ubifs概述_数据_03


LEB属性树区

位置

        LEB属性树区(LEB Properties Tree area,​LPT​ area),跟随在log area之后,其大小在创建文件系统后确定​(固定)​通过LEB 尺寸和文件系统最大LEB count自动计算出LPT area占用的LEB数目。LPT使用​B+树(游离树)​结构,比main区indexing树小的多。LPT area类似一个小型的自包含文件系统,,它有自己的LEB properties。

内容

       LPT properties有​三个值​:​空闲空间(free)​、​脏空间(dirty)​、​擦除块是否是索引擦除块​。空闲空间是一个擦除块的后边的没有被写入的字节数,它可以被填充更多节点;脏空间是被废弃数据(obsolete)或者填充数据(padding)占用的空间的字节数,它可以潜在地被垃圾回收机制回收。这三个值需要被主区(main area)的所有lebs知道。

       详细内容:每个LEB信息(LEB类型:indexing或data,free和dirty space大小),整个flash信息(free和dirty space大小)等,方便UBIFS了解LEB信息。通过此区域可找到可用的LEB,可找到dirty LEB(供GC回收)等。

        注意:索引节点(index nodes)和非索引节点(non-index nodes)永远不会混在同一个擦除块中,因此索引擦除块是只包含索引节点(index node)的擦除块,非索引擦除块是只包含非索引节点(non-index nodes)的擦除块。

相关操作

        重现(replay)必须使得lpt area是最新的(up-to-date)。

        leb properties对于这些操作是很重要的:找到空间来添加到journal或者index;发现最脏的擦除块来垃圾回收。每当有一个节点写入的时候,对应的擦除块的空闲空间必须减少。每当一个节点被废弃(被trunk掉的)或者一个填充节点(padding node)被写入,或者一个截断(truncation)或删除(deletion)节点被写入,对应的擦除块的脏空间必须曾加。(UBIFS 中存在 minI/O,也就是最小的写入数据字节数,如果数据不够,就需要 padding 来填充)。当一个擦除块被分配到这个节点上时,它必须被记录。比如:一个有空闲空间的索引擦除块没有被分配到journal会导致索引节点和非索引节点混合。注意:索引节点和非索引节点不能混合的原因以后再说。

        总的来说,index subsystem本身把leb properties的改变通知给leb properties subsystem。当一个垃圾回收擦除块已经被加入到journal的时候,leb properties引起的replay就会变复杂。比如索引(index),lpt area只在commit时更新。比如索引(index),flash上的lpt在mount的时候不是最新的,必须被replay操作更新为最新的。所以,flash上的垃圾回收的leb的leb properties反应了最后一次commit时的状态。replay将开始更新leb properties,但是有的修改发生在垃圾回收之前,有的修改发生在垃圾回收之后。垃圾回收发生时的点是不同的,导致最终的leb properties是不同的。为了处理它,replay在它的RB树上插入一个引用(reference)来代表是哪一个leb被加入到了journal(使用log reference node的序列号(sequence number),它使得当relay的RB树被用到索引时能正确调整leb properties值。

        master 区中放的是 node 树的根,那么它的枝放在哪儿呢?是以 index node 的形式存放在可擦除块中,所以需要标记一下知道main area 中这个可擦除块中存放的是否是 index node。 LPT area 的大小是根据分区的大小来确定的。LPT 也有自己的 LPT,这是什么意思,就是 LPT 内部建立了一个 ltab(LEB properties table,因为 LPTarea 所占的可擦出块毕竟是少数,所以采用表的形式),是 LPT 表所占 LEB的 LPT。 LPT 也是在 commit 的时候更新的。

        LPT area有自己的垃圾收集器。LPT area要求不能耗光自己的空间,能够快速访问和update,以及算法上的可扩展性。

       LPT area在log area之后。log area的大小在文件系统被创建时定义,同时,LPT area的起始地址也被确定。

LPT area的大小在创建文件系统时通过LEB大小和LEB最大数量计算确定。跟log area一样,LPT area不能用完

自己的所有空间。与log area不一样的是:更新LPT area是随机的,不是连续的。此外,LEB properties数据可能非

常的大,因此要考虑可扩展性解决办法是储存LEB properties到wandering tree中。 事实上LPT区更像是一个

小规模的文件系统。它有自己的LEB properties - 也就是说LEB properties area的LEB properties(称为ltab)。

它有自己的garbage collection。它有自己的节点结构,包装nodes尽可能的紧密。然而,和index一样,

LPT区仅仅在commit时更新。因此on-flash index和on-flash LPT代表的是文件系统最后一次更新前的状态。

它和当前文件系统状态的差异,体现在journal内的nodes上。

      LPT有两种略微不同的形式:small model和big model。

      small model在整个LEB properties table能被写到一个单独的eraseblock时使用。在这种情况下,LPT垃圾回收只是

写整个table,因此会使得其他所有的LPT区域的eraseblocks可以重复使用。

      big model:脏的LPT eraseblocks被选择用来做LPT垃圾回收:标记相应LEB的nodes为脏,然后取消掉(write out)

脏nodes(作为commit的一部分)。对于big model:LEB numbers组成的table被保存,因此在UBIFS第一次挂载时不会

扫描LPT来查找空eraseblocks。在small model中,扫描整个table不会很慢,因为它很小。


孤儿区

        孤儿区(orphan area),在LPT area和main area之间,使用固定数目的LEBs,一般来说占用一个LEB就够了,debug时额外占用一个LEB。

        orphan area保存了已经删除的文件的索引号(inode number),用来从unclean unmount中恢复。

        orphan area的意义在于删除过程unclean unmount发生,已经删除的孤儿inodes必须被删除,这就要求扫描整个index来查找他们,或者在某处保存一个列表,ubifs就是在orphan area保存这样一个列表。

主区

        主区(main area),最后一个area,保存文件系统index node和non-index node,存储实际数据。UBIFS包含几种类型的non-index节点:file inode, directory entry,extend attribute entry和file data node。

其他网址

UBIFS - UBI File-System

《A Brief Introduction to the Design of UBIFS》

UBIFS设计简介(A Brief Introduction to the Design of UBIFS翻译)

ubifs图解