UBIFS文件系统介绍
Big red note
在了解UBIFS之前一定要注意UBIFS和任何传统的文件系统是不一样的:UBIFS不是运行在block device之上的(比如hard disk, MMC/SD卡,USB flash驱动等等)。UBIFS是运行于raw flash之上。请在开始UBIFS之旅前确保理解raw flash和MMC flash的区别
OverviewUBIFS是nokia工程师在the university of Szeged大学帮助下开发的新的flash file system。UBIFS可以认为是JFFS2文件系统的下一代。
JFFS2运行在MTD设备之上,而UBIFS则只能工作于UBI volume之上。也可以说,UBIFS涉及了三个子系统:
1. MTD 子系统, 提供对flash芯片的访问接口, MTD子系统提供了MTD device的概念,比如/dev/mtdx,MTD可以认为是raw flash
2. UBI subsystem,为flash device提供了wear-leveling和 volume management功能; UBI工作在MTD设备之上,提供了UBI volume;UBI是MTD设备的高层次表示,对上层屏蔽了一些MTD不得不处理的问题,比如wearing以及坏块管理
3. UBIFS文件系统,工作于UBI之上
以下是UBIFS的一些特点:
可扩展性:UBIFS对flash 尺寸有着很好的扩展性; 也就是说mount时间,内存消耗以及I/O速度都不依赖与flash 尺寸(对于内存消耗并不是完全准确的,但是依赖性非常的低); UBIFS可以很好的适应GB flashes; 当然UBI本身还有扩展性的问题,无论如何 UBI/UBIFS都比JFFS2的可扩展性好,此外如果UBI成为瓶颈,还可以通过升级UBI而不需改变UBIFS
快速mount:不像JFFS2,UBIFS在mount阶段不需要扫描整个文件系统,UBIFS mount介质的时间只是毫秒级,时间不依赖与flash的尺寸;然而UBI的初始化时间是依赖flash的尺寸的,因此必须把这个时间考虑在内
write-back 支持: 回写或者叫延迟写更准确些吧,同JFFS2的write-through(立即写入内存)相比可以显著的提高文件系统的吞吐量。
异常unmount适应度:UBIFS是一个日志文件系统可以容忍突然掉电以及unclean重启; UBIFS 通过replay 日志来恢复unclean unmount,在这种情况下replay会消耗一些时间,因此mount时间会稍微增加,但是replay过程并不会扫描整个flash介质,所以UBIFS的mount时间大概在几分之一秒。
快速I/O - 即使我们disable write-back(可以在unmount时使用-o sync mount选项), UBIFS的性能仍然接近JFFS2; 记住,JFFS2的同步I/O是非常惊人的,因为JFFS2不需要在flash上维护indexing data结构, 所以就没有因此而带来的负担; 而UBIFS恰恰是有index数据的。 UBIFS之所以够快是因为UBIFS提交日志的方式:不是把数据从一个地方移动到另外一个位置,而只是把数据的地址加到文件系统的index,然后选择不同的eraseblock作为新的日志块,此外还有multi-headed日志方式等技巧。
on-the_flight compression - 存储在flash介质上的数据是压缩的;同时也可以灵活的针对单个文件来打开关闭压缩; 例如,可能需要针对某个特定的文件打开压缩,或者可能缺省方式下支持压缩,但是对多媒体文件则关闭压缩。
可恢复性 - UBIFS可以从index破坏后恢复; UBIFS中的每一片信息都有一个header来描述,因此可以通过扫描这个flash介质来重构文件系统,这点和JFFS2非常类似;想像一下,如果你擦出了FAT文件系统的FAT表,那么对于FAT FS是致命的错误,但是如果擦除UBIFS的index,你人然可以重构文件系统,当然这需要一个特定的用户空间程序来做这个恢复
完整性 - UBIFS通过写checksum到flash 介质上来保证数据的完整性,UBIFS不会无视损坏文件数据或meta-data; 缺省的情况,UBIFS仅仅检查meta-data的CRC,但是你可以通过mount选项,强制进行data CRC的检查
Scalabity
UBIFS文件系统的所有数据结构都是使用tree,所以 UBIFS对flash尺寸算法上是可扩展的。然而,UBI对于flash size是线性增长的,因此UBI/UBIFS stack空间整体上是线性增大的。但是UBIFS的作者认为可以创建一个新的UBI2, 克服当前UBI的线性。当前的 UBI适应2~16GiB大小的raw flashes, 具体依赖与I/O速度和需求。
注意,尽管UBI是线性增长的,但是它的可扩展性依然好过JFFS2,因为JFFS2最初是为32MiB Nor flashes设计的。JFFS2的扩展性是文件系统级的,而UBI/UBIFS的扩展性则是raw flash级的,下表是两个文件系统扩展性的对比
Scalability issur | JffS2 | UBIFS |
Mount time Linearly depends on the flash size | True. 依赖是线性的,因为JFFS2在mount时要扫描整个flash 介质空间 | UBIFS mount时间不依赖flash的尺寸。但是UBI需要扫描flash介质尽管扫描时间要比JFFS2快。所以整体上说, UBI/UBIFS是线性依赖的 |
内存消耗是否线性依赖flash尺寸 | True. 依赖是线性的 | UBIFS 内存消耗的确依赖flash尺寸,因为LPT shrinker还没有实现。但是实现LPT shrinker移除依赖性是容易的。当前没有移除的原因是由于内存消耗很小没必要实现它。 UBI内存消耗是线性依赖flash尺寸,因此整体上说UBI/UBIFS线性依赖 |
Mount时间是否依赖文件系统内容 | True. 存储在文件系统内的数据越多,mount时间就越久,因为JFFS2需要做更多的扫迈工作。 | False. mount时间不依赖文件系统内容,在最坏的情况下,UBIFS不得不扫描日志来replay,而日志是固定大小,并且可配置的 |
mount是需要检查真个文件系统 | True. 当JFFS2 mount到NAND flash上时 不得不检查整个文件系统。 检查包括读取每个inode的所有nodes 检查他们的CRC checksums,这将消耗大量CPU,在mount完JFFS2后,可以通过运行top来观察CPU占用率,这也降低了整个系统启动时间。因为JFFS2没有存储space 管理信息,因此也需要扫描整个flash介质来获取这个信息。 | False. UBIFS不需要扫描整个文件系统,因为UBIFS空间管理信息在LPT(Logical erase block Properties Tree)中 |
内存消耗是否线性依赖文件系统尺寸 | True. JFFS2为flash上的每一个node 保存了一片数据结构。所以存储在文件系统的数据越多,那么JFFS2所占用的内存越多。 | False. UBIFS消耗的内存不依赖存储在flash介质上的数据多少 |
文件访问时间线性依赖文件的大小 | Ture. JFFS2在打开一个文件时,不得不为文件节点在内存保存一棵fragment tree. 这棵树保存着文件的偏移所对应的nodes, fragment tree没有保存在内存中,因此在打开文件时不得不读取这个文件对应的节点来创建这棵fragment tree。这就意味着,文件越大,打开的时间越长。 | False. UBIFS存储所有的index B-tree信息在flash 介质。当要读取文件系统的一片数据,在B-tree查找到对应的flash 地址。当查找B-tree时会把B-tree节点缓存到TNC cache中,cache是可以回收的,这就是说当系统kernel需要更多内存时,可以回收cache |
文件系统性能依赖I/O历史 | True. 因为JFFS2是完全同步的,数据一到达立刻就向flash写入。向一个文件写入很少的字节,JFFS2立即写入包含了这几个字节的node,如果频繁的随机写入,文件系统就会变得碎片化。JFFS2会合并这些碎块为4KiB的块,这涉及到重新压缩和重写数据。这个 去碎片化 随机发生在垃圾收集过程, 因为JFFS2的磨损平衡算法是随机选择eraseblock。所以如果有很多的小写入,JFFS2会在某一刻变得非常的慢,系统性能下降使得系统行为变得不可预测 | False. UBIFS一直写入4KiB的块。数据到达后并不是立刻写入flash,而是推迟以合并后来的写入,当足够的数据要求写入时,才回写到flash上,回写通常是background方式 |
Write-back support
UBIFS支持write-back, 意味着文件的改变并不是立刻提交到flash media上,而是cache这些修改,直到达到写入的条件。这减少了I/O的数目因此改善I/O性能和系统性能。回写本身也是文件系统的标准技术,由于数据没有立刻写入flash, 回写带来了数据丢失的风险。
相反, JFFS2不支持write-back, JFFS2文件系统的所有变化都是立刻同步到flash介质上。事实上,JFFS2有一个很小的buffer大小是NAND page size(如果是 NAND flash)。这个buffer保存这最后要写的数据,一旦buffer写满立刻就会执行flush。 因此JFFS2非常类似于同步文件系统。
write-back支持,需要应用程序注意及时同步重要的文件。否则掉电会导致这些文件的损坏和消失,掉电对于嵌入式系统而言是很常见的。
然而一些应用空间程序并没有考虑write-back这种情况。当这些应用运行在JFFS2上工作良好尽管这些应用是buggy的,一旦运行在UBIFS上,bugs就很容易重现了。在把JFFS2换作UBIFS后,确保检查你的应用是否正确处理掉电功能。下列是检查列表以及一些建议:
1. 如果你想切换到同步模式,在mount文件系统是使用 -o 同步选项。然而,要注意文件系统将会下降。此外,要记住UBIFS mount为同步模式仍然不如JFFS2提供更多的保证
2. 一定要时刻记住运行fsync在你修改重要数据后;当然,没有必要sync临时的文件;要先考虑文件数据的重要性,不要执行没必要的fsync, 因为fsync操作会降低性能
3. 如果你想更精确些,那么就使用fdatasync, 仅仅修改的数据被flushed, 而inode meta-data变化不会被flush(比如mtime或者permissions)
4. 你也可以在open()调用时是一年个O_SYNC标志;这使得这个文件所得修改的data(不包括meta-data)都会在write()操作返回前写入media;通常来说,最好使用fsync(), 因为_SYNC使得每个写都是同步的,而fsync允许多个累积的写
5. 还可以使一定数目inodes为同步模式,通过设置inode的sync标志; 在shell中执行chattr +S; 在C程序中,则可以使用FS_IOC_SETFLAGS ioctl命令; 注意, mkfs.ubifs工具会检查原始的FS树,如果文件在原始文件树是同步的,那么在UBIFS image也会是同步的
要强调的是,上面的方法对于任何文件系统都是可行的,包括JFFS2
fsync()可能包括目录 - 它同步目录inode的meta-data。 “sync” flag也可以用在目录上,使得目录inode变成同步的。但是"sync" flag是可继承的, 意味这这个目录下的所有新节点都有这个标志。 这个目录的新文件和新子目录也就变成同步的, 子目录的child也是如此。 这个功能是非常有用的,如果想创建一个需要整个目录树都同步的目录。
fdatasync()调用对 UBIFS的目录是不起作用的, 因为UBIFS对目录项的操作都是同步的,当然不是所有文件系统都如此。类似的, "dirsync" inode 标志对UBIFS没有作用
以上提到的功能都是作用于文件描述符,而不是文件stream(FILE *)。 同步一个stream, 你应该通过libc的fileno()取得文件描述符, 首先使用fflush()来flush stream ,然后调用fsync或者fdatasync. 你也可以使用其他的同步方法,但是记得在同步文件前要先flush stream. fflush() 和sync(), fsync, fdatasync的区别在于前者仅仅同步libc-level的buffer,而后者则是kernel-level buffers
Write-back knobs in Linux
Linux有几个内核参数可以用来调整write-back,可查看/proc/sys/vm. 这些参数是global, 所以会影响所有的文件系统。 参考Documentation/sysctl/vm.txt获取更多的信息。1. dirty_writeback_centisecs: linux周期性write-back线程写出dirty数据的周期,这个机制可以确保所有的脏数据在某个时间点都可以写入介质
2. dirty_expire_centisecs: dirty数据的过期周期,这是数据为dirty的最大时间。 过了这个时间,dirty数据会被周期性write-back线程写回介质。换句话说,周期性write-back线程每dirty-writeback-centisecs 时间唤醒,然后同步那些dirty_expire_centisecs时间之前就已经dirty的数据
3. dity_background_ratio: dirty数据与全部内存的最大百分比。 当脏数据变多的时候,周期性的write-back线程开始同步数据使得脏数据比例变小。这个过程包括那些non-expired的数据。 这个可以认为是系统dirty数据的soft limit
4. dirty_ratio: dirty数据与全部内存的最大百分比,超过这个显示,writes同步数据先于增加dirty数据。这个是系统diry 数据的hard limit
UBIFS write-buffer
UBIFS是一个异步文件系统,和其他文件系统一样,UBIFS也利用了page cache. page cache是一个通用的linux内存管理机制。page cache可以非常大以cache更多的数据。当你写一个文件时,首先写到page cache中,置为dirty, 然后write操作返回(特例是文件是同步的)。 后面数据会通过write-back机制写回
write-buffer是UBIFS特定的buffer, 它位于page cache和flash介质之间。这意味着write-buffer 只是写到write-buffer上 而不是flash介质
write-buffer 是用来优化UBIFS在NAND flashes上的速度。 NAND flashes包含NAND pages, 页是NAND flash的最小读写单位,通常为512, 2KiB或者4KiB
write-buffer尺寸等于NAND page size. 他的目的是积累samll writes为一个满的或者部分满的page。考虑下面的例子,假定我们写了4x512bytes nodes在半秒内,page size是2kiB的情况下。如果没有write-buffer,那么分开的写会使用4个page,浪费6KiB的空间,而write-buffer 可以仅写一次,并且没有空间浪费。这意味着,write-buffer避免生成过多的dirty space, UBIFS的垃圾收集所做的工作就越少。
当然上面的例子是一个理想状态,即便write-buffer也可能浪费空间,比如使用同步I/O或者数据抵达的时间间隔较大。因为write-buffer也有个过期时间,每3-5秒即便write-buffer不满,也会写出。这是为了数据完整性的原因
当然,如果UBIFS写很多的数据,那么就不使用write-buffer. 仅仅数据的最后一部分由于小于NAND page尺寸需要等待填满page的数据, 直到write-buffer的定时器到时
write-buffer实现上有一点复杂,UBIFS中使用了一些write-buffer, 每个journal head都有一个。 当然这并不改变write-buffer的基本机制
关于同步有几点需要注意的:
1. sycn()会同步所有的write-buffers
2. fsync(fd)也会同步 fd相关的所有write-buffers
3. 用O_SYNC打开文件,会忽略write-buffers, 所以I/O会直接写入介质
4. 如果文件系统mount时使用了-o sync选项,那么write-buffers也会被忽略
在数据同步时要考虑write-buffer的timer时间,在synchronization timout "dirty_expire_centisecs"加上额外的3-5秒,当然由于write-buffer很小,所以delay的数据很少
UBIFS in synchronous mode vs JFFS2
mount UBIFS文件系统使用-o sync标志,所有文件系统的操作都变成了同步模式,意味着在file-system操作返回前,所有的数据都写入了flash介质
例如,如果你使用write()写10MiB数据到文件f.data, 并且UBIFS是同步模式。在write()返回前所有的10M文件数据以及meta-data(file size, date changes)也写入了flash介质。此时即便掉电,这个文件的数据也会包含写入的数据
在打开文件时,使用O_SYNC标志也有同样的效果。
在突然掉电的情况下,UBIFS并不能像JFFS2那样提供较多的保证。
在JFFS2中,所有的meta-data存储在data node headers中。数据节点包含4KiB的压缩数据。这意味meta-data信息在flash有多个版本,每次JFFS2写一个data node到flash介质上时,都会更新文件的inode size。当JFFS2mount时,会扫描flash介质,发现最后的data node,同时获取inode的size
在实际使用中,JFFS2会顺序的写这10M数据,从头到尾。如果中间发生掉电,那么就丢失掉结尾的数据。比如写10M的数据到JFFS2中,写了5M,此时掉电发生,那么可以得到一个5MB的文件,而丢掉了未写入的5MB
对于UBIFS来说有一点点复杂,数据被存储在data nodes中,而meta-data被存储在分离的inode nodes中。meta-data在每个数据节点中没有像JFFS2一样有duplicated的版本。UBIFS从来不会写on-flash inode size外的数据节点。如果不得不写data node, 并且data node是在on-flash inode size之外(in-memory inodes已经更新为up-to-data尺寸,但是脏的还没更新到flash上),那么UBIFS首先要把inode写入介质,然后才能开始写数据,此时掉电就可能会产生holes文件,看下面的例子
1. 用户创建一个空文件f.dat. 文件是同步的, 或者UBIFS文件系统被mount为同步模式。user调用write()函数来写一个10MiB buffer.
2. kernel首先拷贝所有的10MiB数据到page cache. Inode size 被修改为10MiB and inode被mark为dirty。 没有任何数据和meta-data写入flash media, 此时如果发生掉电,那么user看到的是一个空文件f.data
3. UBIFS看到I/O是同步的,那么首先开始同步inode,首先写inode node 到flash media中,此时如果发生掉电,那么user会看到一个10MiB大小的空文件
4. UBIFS开始写数据. 如果掉电发生在这一点上,那么user看到一个10Mib大小的文件,在文件后部是有个hole
注意,即便I/O不是同步的,UBIFS在开始写数据 meta-data到flash media前就返回。write-back将在某个时刻开始写flash media, 如果在write-back过程发生掉电,那么同样会产生hole
因此,UBIFS并不能像JFFS2那样,很完美的保留掉电前写下的数据以及文件尺寸,主流文件系统比如ext3也没有提供JFFS2类似的功能。
虽然,有时人们用UBIFS替代JFFS2,人们希望UBIFS能够像JFFS2一样行事,这是可行的,需要hack一下UBIFS代码。之所以UBIFS还没有这样实现,是因为这个需求并没有那么强烈。或者自己实现它或者说服UBIFS作者实现这个功能。
Synchronization exceptions for buggy applications
UBIFS作为一个异步的文件系统,application应该在需要的时候同步他们的文件。这同样适用于大部分linux 文件系统然而,一些应用忽略了这一点,没能够正确的同步文件。
。。。。。。。。。
。。。。。。。
Compression
UBIFS支持快速的压缩,UBIFS在把数据写入flash media之前压缩数据,在读出来时解压数据,这个compress/decompress过程对与用户来说是完全透明的。UBIFS仅仅压缩文件数据。目录,设备节点等是不压缩的。Meta-data和indexing信息也不压缩的
当前UBIFS支持LZO和zlib压缩器。ZLib提供了更好的压缩率,但是LZO在压缩和解压时的速度更快。LZO是UBIFS和mkfs.ubifs的缺省压缩处理器。当然你可以在mkfs.ubifs命令选项中使用-x来disable UBIFS压缩。
UBIFS把数据分割为4KiB的块然后对每一个chunks进行单独压缩。这不是最优的,因为大块的数据压缩效果更好,即便如此仍然提供了客观的flash空间效益。比如,一个真实的root file system镜像利用LZO压缩器可以减小40%空间,而用zlib压缩器则减少50%空间。这意味如果你可以把一个300MB的文件系统放到256MiB的UBI volume中,而且荏苒有100MiB的空闲空间。然而这可能随着文件系统存放的内容不同而不同,比如,如果你的文件系统保存的都是mp3文件,UBIFS无法有效的压缩他们,因为mp3文件本身就是被压缩的。
在UBIFS中是可以针对每个文件节点来disable/enable压缩功能的,方法是设置或者清除inode节点的compression标志。注意,这个压缩标志对于目录是可继承的,也就是说这个目录下的文件和子文件在创建时,他们的compression标志是继承自父目录
还要注意的是JFFS2 LZO压缩和UBIFS的zlib压缩有轻微不同。UBIFS使用crypto-API缩小方法,而JFFS2直接使用zlib库。导致UBIFS和JFFS2使用不同的zlib压缩选项。名义上,JFFS2使用缩小level3,window bits15,而UBIFS使用level6 windows bit -11(负号使得zlib避免放一个header到数据流中)。经验显示JFFS2压缩率稍微小一些,解压速度慢一些,但是压缩速度要快一些。
Checksumming
UBIFS写入到介质的每一片数据都有一个CRC32 checksum。UBIFS使用CRC来保护data和meta-data。每次读meta-data时,CRC校验码都会被验证。CRC-32校验是非常强大的,几乎所有的数据损坏都可以检查到。UBI也是如此,验证每一片meta-data。
data CRC在缺省情况下是不验证的,这能改善文件系统的读速度,当然UBIFS可以在mount时用chk_data-crc,来切换为data验证。这会减少UBIFS的读速度,但是提供了更好的数据完成性保护。使用这个选项,UBIFS读取的每一片信息都会被验证,任何数据损坏也都会被检查到。
注意,当前UBIFS不能disableCRC-32的写计算,因为UBIFS在recovery过程依赖于它。当从一个unclean reboot恢复,replay日志时,UBIFS需要检测broken和写了一半的UBIFS nodes然后抛弃他们,在这里UBIFS依赖于CRC-32 checksum
换句话说,如果你使用UBIFS时disable掉CRC-32,但是你仍然为每一片数据生成了CRC-32 checksum, 你可以随时激活读校验
注意:在2.6.39之前default UBIFS行为是相反的, UBIFS缺省支持CRC-32, 使用no_chk_data_crc来disable它
Read-ahead
read-ahead是一个文件系统的优化技术,每次读取的数据比用户请求的数据多一些。这个主意是基于文件的访问一般都是从开始向后顺序进行的,所以文件系统试着把将来可能用到的数据提前读出了。
LINUX VFS本身会做这个read-ahead而不需要file system的支持。这对于传统的块设备文件系统工作良好,但是UBIFS不会得益于此。UBIFS工作于UBI API之上,而UBI工作与MTD API之上,MTD API是同步地, MTD API是非常简单的,没有任何request queue。这意味这VFS 阻塞住 UBIFS readers, 让他们等待read-ahead过程。block-device API是异步的 readers不需要等待read-ahead.
VFS read-ahead是为hard drives设计的。但是raw flash设备和hard drive是非常不同的, raw flash设备不需要耗时的寻道时间,所以适合hard disk的技术并不适合flash设备
也就是说,VFS的read-ahead仅仅会使得UBIFS变慢,所以UBIFS 会disable 掉VFS的read-ahead。但是UBIFS有他自己内部的read-ahead,我们称之为bulk-read。你可以在mount时增加“bulk_read”选项来使能bulk-read功能.
有些flash可能一次读出全部数据要比分多次读出数据要快。例如,OneNAND可以做read-while-load如果它读取超过一个NAND page。所以UBIFS可以通过一次读取较大的data chunks来获取性能上的提升,这就是bulk-read 要做的
如果UBIFS注意到一个文件正在被顺序的读取,UBIFS看到接下来的数据就存放在相同的eraseblock中,则UBIFS开始read ahead数据,通过使用大的read request。UBIFS把这些提前读取的数据放到文件的cache中,所以用户的进一步读取可以直接从cache中获得。
很明显,bulk-read 在某些情况下,可能会减慢UBIFS,所以要小心。此外注意在高度碎片化的文件系统上,bulk-read并不适合。尽管UBIFS不会主动碎片文件系统,但是也不会de-fragment文件系统。比如数序的写一个文件,那么不会有变得碎片化。但是如果你同时写多个文件,他们就可能变得碎片化(这也依赖于write-back怎么样提交更新),UBIFS不会自动de-frament他们。当然,可以实现一个background的defragmenter. 我们也可以使用per-inode的日志头来避免在一个LEB上混合属于不同的inode的数据节点。所以仍然有改善的余地。
Space for superuser
UBIFS问超级用户保留了一些空间,这意味着当文件系统对于普通用户为满时,仍然有一点保留空间给 super-user。其他文件系统比如ext2也有类似的特点,mkfs.ubifs有一个-R选项可以用来标识保留空间的尺寸。
缺省情况下仅仅root可以使用保留空间,但是可以扩展特权用户的列表。UBIFS可以记录几个user和group IDs在超级块中,允许他们利用保留空间。尽管当前的mkfs.ubifs工具没有相应的命令行选项,但是很容易实现这个功能。
注意,UBIFS在mount文件系统时会输出保留空间的数目。
Extended attributes
UBIFS支持扩展属性: user, truested and security name-spaces. ACL支持还没实现。注意,当前mkfs.ubifs忽略扩展属性,没有把他们写入target文件系统image
Mount options
下面是UBIFS-specifi特定的mount选项1. chk_data-crc
2. no_chk_data-crc
3. bulk_read
4. no_bulk_read
此外UBIFS支持标准的sync mount 选项,可以用来disable UBIFS write-back和write-buffer,使得写过程完全同步。
注意UBIFS不支持atime,所有atime mount选项不起任何作用。
Flash space accounting issues
传统的文件系统,比如ext2可以很容易的计算出空闲空间。计算是非常精确的,用户对此已经习以为常。然后,UBIFS则完全不同了,UBIFS无法汇报它还有多少空闲空间可用。只能汇报最少的可用空间数,这个数目通常会少于实际的数目,有时差错甚至很大。例如,UBIFS汇报没有空闲空间,但是仍然可以写入很多的数据。
之所以UBIFS汇报的空闲空间的数目少于实际拥有的,是基于以下几个理由,我们逐一讨论。
Effect of compression
第一因素是UBIFS的快速压缩。用户一般认为文件系统汇报了N bytes空闲空间,那么就意味着可以写入N bytes的文件数据。因为压缩的存在,这种想法就不成立了。根据数据压缩程度的不同,UBIFS可以写入的数据可能数倍于汇报的空闲空间当UBIFS计算空闲空间时,他并不知道即将写入的数据的特性,因此无法考虑压缩,所以就假定为最坏的情况,数据没有压缩。
虽然这看起来问题不大。但是综合考虑压缩和write-back,压缩就变成了估算空闲空间的大问题。换句话说,UBIFS无法知道cached dirty数据的压缩,想知道结果的唯一办法就是执行压缩
Effect of write-back
假定在page cache中有X bytes的脏数据,他们将在某个时间被刷如flash media。UBIFS当前需要X+O bytes空间来写这些数据,O是文件系统开支(比如数据的index,以及每个data node需要一个header)问题是UBIFS无法精确的计算出X和O,它使用悲观的最坏情况的计算,所以当cached data被刷入flash, 所需的flash空间可能原少于需要的X+O,例如看下面的情况
$ df Filesystem 1K-blocks Used Available Use% Mounted on ubi0:ubifs 49568 49568 0 100% /mnt/ubifs $ sync $ df Filesystem 1K-blocks Used Available Use% Mounted on ubi0:ubifs 49568 39164 7428 85% /mnt/ubifs
第一次df汇报zero空闲空间,但是sync后汇报了15%的空闲空间。这是因为有很多cached的dirty data, UBIFS为他们保留了所有的空闲空间,但是在flash media过程中,仅使用了部分flash space。
下面是UBIFS为什么保留这个多空闲空间的理由
1. 还是关于compression。数据以uncompressed形式存储在cache中,UBIFS并不能预估这些数据的压缩,所以它假定数据完全不能压缩。然而,现实生活数据是很容易压缩的,除非.tgz或者.mp3文件。这就导致了对X的过分估计。
2. 归咎于设计,UBIFS nodes不会跨越earseblock的边界,所以在每个eraseblock的末尾处都会有一些小的浪费空间。这个浪费的flash空间依赖于数据写入和更改的顺序。传统的UBIFS会悲观的估计这个浪费空间为最大值,这也导致了对O的过份估计。
因此UBIFS在同步后可以更精确的估计空闲空间值
Wastage
像上面提到的,UBIFS不会跨越LEB边界。考虑下面数字:1. UBIFS node最大尺寸是4256字节
2. UBIFS node最小尺寸是56 字节
3. 依赖于名字长度,目录项节点占据56~304字节
4. LEB尺寸:典型的NAND flash 128KiB的物理擦除块 2048字节的NAND page,LEB的尺寸是126KiB(或者124KiB如果NAND chip不支持sub-pages)
因此,如果大部分flash上的节点是非压缩的data nodes, UBIFS将会浪费126KiB LEB的最后1344bytes。但是现实生活中的数据通常是压缩的,所以node尺寸可能发生变化,因此在删除块末尾的wasted space范围为0~4255。
UBIFS把一些小的节点,比如目录项节点放到LEBs末尾来减少wasted space,但是不理想,UBIFS仍然在LEB的末尾浪费掉大块的空间。
当汇报空闲空间时,UBIFS不知道哪种数据,以何种顺序写入flash media。因此,它评估每个LEB的wastage为最大可能值4255。这个计算对大多数现实世界都是太悲观了,真实的wastage要远少于4255。然而,UBIFS汇报给用户空间程序的是绝对最小值。
以上意味着LEB越大,UBIFS对空闲空间的预测越准确,比如128KiB擦除块要好于16KiB的参数块
Dirty space
Dirty space是曾经别UBIFS nodes使用过的空间,但是由于改变或者removed导致这个空间变得无效。例如,如果文件的内容被re-write,那么相应的数据node被无效,新的数据data写入flash media。无效的节点包含dirty space,还有其他的机制会导致dirty space出现。UBIFS无法直接重用dirty space。因为相应的flash areas没有包含所有的0xff bytes。在dirty space可以重用前,UBIFS不得不垃圾收集相应的LEBs。垃圾收集回收dirty space的方法和JFFS2是相同的。请参考JFFS2 design document
粗略的讲,UBIFS垃圾收集选取一个LEB,这个LEB包含一些dirty space,然后把这个LEB上的有效UBIFS节点移到GC reserverd LEB上。这将消耗GC reserved LEB的一些空闲空间,GC选取另外一个需要收集的LEB,然后把有效数据移到这个GC reserved LEB,持续这个过程直到LEB变满。GC选取另外一个 reserved LEB,继续这个过程。
UBIFS有一个概念叫最小I/O 单位, 描述可以写入flash数据的最小数目。一般情况下UBIFS工作在large-page NAND flash上,最小I/O单位是2KiB
事实上,UBIFS GC虽然尝试不浪费任何空间,但是有时并不能如愿。UBIFS不一定能够回收那些dirty空间小于I/O unit的dirty space
当UBIFS向users汇报空闲空间时,对待dirty space为可用空间,因为GC可能会回收这部分空间为空闲空间,但如上面分析的,UBIFS无法回收所有的dirty space为空闲空间,更坏的情况是UBIFS无法确切知道它可以回收多少dirty space。再一次UBIFS使用最悲观的计算。
因此dirty space越少,UBIFS汇报的free space越准确。在实际应用中,这意味着一个满的文件系统要比一个空的文件系统更难预测空闲空间,因为满的文件系统有太多的dirty space。
注意,解决这个问题,UBIFS可以在statfs()中运行GC, GC将把dirty space变为free space, 因此预测空闲空间就变得更精确。然而,这将导致statfs变得非常的慢,因此调用statfs的应用的行为将不可预测。此外也可以使用JFFS2使用的background GC。
Precise index size is not known
大概你已经知道,UBIFS在flash media上维护文件系统的index。index占据flash的一部分空间。UBIFS journal保存着FS data。journal中的FS data没有index, 这意味着on-flash index没有指向他们。 journal FS data的index是建立在RAM中的,当文件系统被mount时, UBIFS要扫描整个journal区,然后在RAM中重建index。journal某种程度像是UBIFS中的一个JFFS2文件系统。journal data在提交时会变成indexed。在UBIFS提交时,更新on-flash index,使得index指向journal data。然后UBIFS挑选其他的LEBs作为新的journal, 所以journal在提交后会改变位置。这就是UBIFS的journal特点,日志区不是固定的。
UBIFS精确的记录着index size。也就是,UBIFS一直很清楚当前on-flash index占据了多少空间。然而,UBIFS无法精确的预测在提交后on-flash index要增加或减少多少。再一次的,UBIFS考虑最坏的情况。
当然在提交后UBIFS再次知道index的确切尺寸。sync()不仅仅flush左右的脏数据,而且也会提交journal。这意味者同步过的文件系统对空闲空间的预测更准确。
事实上,UBIFS本可以精确的预计index size而无须commit操作,但是UBIFS作者并没有实现它,因为实现很困难,而index size的影响又很小。
Documentation
如果文件系统对你来说是一个完全崭新的领域,那么推荐你首先从JFFS2开始,因为UBIFS的很多想法来源于UBIFS。参看 JFFS2 design文档JFFS3 design 文档,你可以发现很多JFFS2问题的描述,以及很多UBIFS的基本想法。记住,这个文档比较老有些内容过时了。现在我们不再使用JFFS3这个名字,JFFS3已经改名为UBIFS。在写这个文档时UBI并不存在,当时假定JFFS3直接运行在MTD设备上。当然JFFS2 overview, JFFS3 Requirements和Introduction to JFFS3章节大部分仍然是正确的并且对UBIFS的想法给了准确的介绍,比如wandering tree和journal。请注意,superblock的描述对UBIFS是不正确的,UBIFS是基于UBI不需要这个技巧。当然,superblock的位置注意可以用于UBI2 layer
这个文档以及UBIFS FAQ保存着关于UBIFS的大量信息。然而你也需要研究UBI,因为UBIFS是依赖于UBI层提供的服务。参考UBI document和UBI FAQ
UBIFS white-paper 对于新手来说非常的难,所以我们推荐你首先从JFFS3设计文档开始。UBIFS white-paper包含UBIFS设计的总体图,并且描述了UBIFS的内部。white-paper没有包含本文档和UBIFS FAQ的一些细节
最后是UBIFS source code. 代码中包含了大量注释,所以我们推荐你在这里找到你需要的细节。最后欢迎你到UBIFS mailing list提问
Raw flash vs. FTL devices
FTL 全称 "Flash Translation Layer", 是一个软件层用来在flash hardware上模拟一个block device。在早期 FTL运行在host computer上,今天FTL通常都是fireware, 运行在存储设备内建的控制器上。例如,如果你查看一个USB flash盘,那么你可以看到一个NAND chip和一个micro-controller, 这个控制器运行FTL firmware. 一些USB flash deivces甚至有强大的ARM处理器。类似的,MMC, eMMC, SD, SSD和其他的FTL devices有一个内建的处理器来运行firmware。所有的FTL设别都提供了一个block I/O访问接口。虽然这些接口有不同的规范定义的,比如MMC, eMMC, SD, USB mass storage, ATA。但是他们都提供了对块设备的访问。基于块设备的访问意味着整个设备可以看作是一个线性blocks的排列。每一个块都可读可写。
尽管大部分常用的flash hardware有FTL, 但是仍让存在一些裸flash没有FTL。 比如各种手持设备和嵌入式系统。裸flash设备和block设备是非常不同的。他们的工作模式不同,裸flash和block设备相比,有更多的限制和问题需要软件考虑这些问题。使用FTL层,这些限制和问题都可以被FTL屏蔽。
UBIFS文件系统被设计用来使用raw flash,假定是raw flash设备模型,他无法工作在block device之上。换句话说,它认为设备有很多eraseblocks,可以写入,读出,或者擦除。UBIFS负责考虑以out-of-place方式写数据,做垃圾收集等等。UBIFS利用UBI来实现磨损平衡和坏块管理,正常的block drive都不需要处理这些事情。
人们经常会问:问什么有人需要使用raw flash而不是使用eMMC或者类似的块设备? 这个问题回答起来有点复杂,以下是UBIFS开发者的几点想法。请记住这个观点的时间(作者无法保证这些观点随着时间的改变而变得不正确),用户也要根据自己系统的实际情况来考虑这几点。
1. 裸NAND 芯片更简单 更便宜,然而,随着业界推动FTL设备,情况似乎在发生变化。事实上FTL设备远比raw flash设备复杂,因为FTL不得不内建额外的控制器。但是因为业界生产了大量的FTL,FTL的价格在下降。
2. 如果你使用一个FTLs设备,并且打算在上面跑FAT file system。那么你应该选择FTL device(eMMC, MMC, SD等等),确保FTL设备可以正确的执行wear-leveling
3. 当你想使用FTL设备作为系统存储,比如rootfs,并且想使用更可靠的文件系统比如ext3。在这种情况下,要考虑各种系统需求比如对突然掉电的容忍度?下面的情况大部分是关于系统存储情形的
4. FTL devices是一个黑盒。FTL算法是vendor的秘密,但是你知道NAND flash有磨损平衡,坏块管理,read-disturb等问题。如果这些问题对你很重要,特别是当使用MLC NAND flash(寿命仅为1000 erase)。因为FTL算法是封闭的,所有很难确保是否FTL device正确的处理了这些问题。
5. 如果你开始考虑FTL是怎么实现的,你可能意识到他必须实现GC。flash hardware需要写是out-of-place的。但是FTL在通然断电的情况下工作如何呢,如果通然断电发生在垃圾收集的过程中呢? FTL device是否保证断电前的数据不失踪,不损坏呢?
6. 掉电tolerance可以被测试,但是磨损平衡和read-disturb很难被测试,因为需要很多时间来测试他们。
7. 我们听说过一些flash USB设备很快的磨损了。比如flash汇报I/O错误在经过几周的密集使用后。这意味着USB device没有做好wear-leveling,当然这并是说所有的USB device都不好用,而是说你要注意这一点。
8. 我们听说一些MMC, eMMC和SD cards在写的过程掉电导致数据丢失和损坏,甚至在很久以前就存在的数据发生了丢失和损坏。这表示他们的FTL没有正确处理。再次强调,并不是所有的MMCs/eMMCs SDs有同样的问题
9. 回顾历史,许多FTL设备使用FAT文件系统存储图片和video。FAT文件系统不稳定,导致了FTL设备可能也不稳定。当然丢失几幅图片并不是什么大事,但是确保system libraies不要因为掉电而损坏。
10. 好的FTL必定是一个复杂的软件,特别是当FTL去处理MLC NAND。在firmware中实现他们很困难,运行他们需要强大的控制器。我们怀疑一些厂商使用了各种技巧和妥协来使得他们的设备足够好和便宜。例如,众多周知有些厂商针对FAT优化他们FTL,但是如果你在他们上面使用ext3,那么你就可能会碰到很多不可预知的问题,设备可能会出乎意料的变化,当然,对于封闭的FTL你没有办法验证
11. SSD驱动和eMMC MMC/SD是非常不同的。他们很昂贵,而且使用强大的CPU来运行复杂的软件,因此他们可能工作的很好
12. FTL设备变得越来越便宜,同时也越来越好,尽管我们很难界定一个设备是好还是坏。通常来说使用FTL没什么不对,因为你相信它,也测试过它,或者仅仅你的需求很适合用FTL
13. 使用raw flash的好处是,你知道你正在做什么,UBI/UBIFS处理NAND flash的所有事情,包括bad erase-blocks和wear-leveling。它保证掉电处理。同时它是开放的,你可以验证,测试,修改它。没有隐藏它能做什么不能做什么。另一方面,使用FTL你没有办法看到内部正在发生的事,FTL厂商可能骗你说他们的FTL设备有多好。当你发现一个bug时,开发商也不一定很快的给予反馈。
14. 理论上,UBIFS可以更好的工作因为他比FTL知道更多的信息,例如,UBIFS知道删除的文件,而FTL不知道,所以FTL可能为已经删除的文件保留sectors。然而,一些FTL设备可能支持"discard"请求,所以能处理来自文件系统的提示。无论如何,UBIFS在裸的NAND可以更好的工作。当然,FTL设备可以包括多个NAND chips,这就有了并行的可能来提高I/O速率
15. 很明显,FTL的优点是你可以在它上面使用老的可信的软件。但是注意,有时这并不完全正确。UBIFS作者曾经测试过一个知名的eMMC,在掉电时产生了严重的问题。此外,这个eMMC不能和ext3很好的配合,因为当这个FTL掉电时,会出现sectors不可用,读操作会返回ECC错误。对于ext3来说read errors是致命的,因为文件系统没有考虑这种情况。fsck.ext3工具也没法修理汇报read errors的文件系统。
所以很难有一个标准答案。在你的系统需求和决定中考虑正反因素。无论如何,raw flashed在嵌入式世界中广泛使用,这也是为什么开发UBIFS的原因。