全部博文(28)
分类: LINUX
2010-07-27 20:45:51
Log区后面是LPT area。log区的大小当文件系统被创建的时候定义创建的,也就是顺序地就是LPT区的开始。目前,LPT区的大小是基于LEB大小和当文件系统创建时指定的最大的LEB数目自动计算的。和log区一样,LPT区必须永不超出空间。不像log区是,LPT区更新不是自然顺序的,他们是随机的。另外,LEB properties 数据的数量潜在地非常巨大的,通过它必须是可量的。解决方法是存储LEB properties 到一个wandering tree。实际上LPT区非常像一个微型的文件系统。它有自己的LEB properties,那就是LEB properties 区的LEB properties(称着ltab)。它有自己的垃圾回收。它有自己的节点结构。无论如何,和index一样,LPT区只在提交时更新。因此on-flash index和on-flash LPT描绘最后一次提交文件系统是什么样的。和文件系统的实际状态不同点被日志中的节点描述。
LPT实际上有两个稍微不同的形式,称为小模式和大模式。当整个LEB properties table 可以写到一个eraseblock,小模式被使用。在那种情况下,LPT垃圾回收就是写整个表,这因此导致所以其他LPT区eraseblocks 可重复使用。在大模式下,脏LPT eraseblocks被选择为垃圾回收,由记录LEB的节点脏和写脏节点组成。当然,在大模式下,LEB号的表被存储以至于当UBIFS第一次挂载时,寻找空eraseblock不会搜寻整个LPT。在小模式,它是假设搜寻整个表不是很慢的,因为它很小。
UBIFS的一个主要任务是通向index,它是一个wandering tree。为了使其更有效,index nodes被缓存在内存中一个叫tree node cache(TNC)的结构里。TNC是一个B+tree,和on-flash index相同的node for node,添加自从上次以来的所有改动。TNC的nodes称为znodes。另外一种看法是一个znode,当on-flash被称为一个index node,一个index node在内存中称为一个znode。最初是没有znode的。当在index上搜寻时,index nodes需要读出来,将他们当作znodes添加到TNC。当一个znode需要改变,就将其标记为脏放在内存中直到下一次提交它又再一次变为干净。在任何时候,UBIFS内存shrinker可能决定释放TNC中的干净的znodes,以至于大量需要的内存和部分使用的index大小相称,注意是全部大小。另外,放开TNC的底部是一个leaf node cache(LNC),它是只用来为目录项的。LNC需要缓存被读的节点是碰撞解决或是目录读操作的结果。因为LNC依附于TNC,这个有效地得到收缩(shrink)当TNC做此操作时。
进一步描述TNC复杂性使提交和其它UBIFS操作产生尽可能少的冲突。为了做这个,提交被分成两个主要部分。第一个部分叫提交开始(commit start)。在提交开始期间,提交写信号量down,这防止期间对日志的更新。在这期间,TNC子系统产生很多脏的znodes和找到他们将被写入flash的位置。然后提交释放信号量,一个新的日志开始被使用,同时提交的过程在继续。第二部分叫提交结束(commit end)。在提交结束期间,TNC写新的index nodes,但是不带任何锁(即类似前面的信号量)的。也就是TNC可以被更新同时新的index可以被写到flash中。这是通过标记znodes完成的,称为copy-on-write。如果一个znode提交时需要被改变,那么将拷贝一份,以至于提交看到的仍然是没改变的znode。另外,提交是UBIFS的后台线程运行的,这样用户进程对于提交的只需等待尽可能少的时间。
接下来LPT和TNC采用了相同的提交策略,他们都是B+tree组成的wandering trees。这导致了代码方面很多的相似性。
UBIFS和JFFS2之间有三个重要的不同点。第一个已经提到过了:UBIFS有on-flash index而JFFS2没有。第二个不同点是暗含的:UBIFS运行在UBI层(运行在MTD层)上的,而JFFS2直接运行在MTD层。UBIFS得益于UBI的损益平衡和错误管理,这些占用的flash空间、内存和其它资源都是有UBI分配。第三个重要的不同点是UBIFS允许回写(writeback).
Writeback是VFS的一个特征,它允许写data到缓存中不立即写到介质中。这是系统更好潜在地更有效,因为对同一个文件的更新可以一块分组。支持writeback的困难是这个要求文件系统知道有多少剩余空间是有效的以至于缓存永远不要大于介质的空间。对于UBIFS,这点是非常困难决定的,所以一个整个称为预算(budgeting)的子系统专门做这个事情。困难有好几个理由。
第一个理由就是UBIFS支持透明的压缩。因为压缩的数量是不知道的,需要的空间的数量是不知道的。预算必须假设最糟的情况,假设没有压缩。无论怎么样,多数情况下是一个不好的假设。为了克服这个,当察觉到空间不足时预算开始强制回写。
第二个理由是垃圾回收不保证能收回所有的脏空间。UBIFS垃圾回收一次处理一个eraseblock。如果是NAND flash,只有完整的NAND页可以写。一个NAND eraseblock由固定数量的nand pages组成。UBIFS称nand page大小为最小的I/O单元。因为UBIFS一次处理一个eraseblock,如果脏空间少于最小的I/O大小,它是不能被回收的,它将以填料结束。当一个eraseblock的脏空间少于最小I/O大小,那个空间称为死区(dead space)。死区是不回收的。
类似于死区,还有一个叫暗区(dark space)。暗区是一个eraseblock的脏空间少于最小node大小。最坏的情况,文件系统满是最大大小的nodes,垃圾回收将没有结果在多片剩余空间。所以在最坏的情况下,暗区是不回收的。在最好的情况下,它是可以回收的。UBIFS预算必须假设最坏的情况,所以死区和暗区都被假设为无效的。无论如何,如果有不充足的空间,但是有很多暗区,预算自身运行垃圾回收看是否能释放更的空间。
第三个理由是缓冲数据可能是存储在flash 上的过时数据。是否是这种情况通常是不知道的,压缩中有什么不同点一般也是不知道的。预算强制回写当它计算不充足空间时。只有试着回写、垃圾回收和提交日志后,预算将放弃并返回ENOSPC(没有空间错误码)。
当然,那就意味着当文件系统接近满时,UBIFS将变得很无效。实际上,所有falsh文件系统都是这样。
第四个理由是删除和截断需要写新节点。因此如果文件系统真的没空间了,它将不可能删除任何东西的因为已经没有空间来写删除节点的节点或者截断节点了。为了防止这种情况,UBIFS经常保留一些空间,允许删除和截断。
下一个UBIFS区是孤立区(orphan area)。一个orphan是一个节点号,该节点号的节点的节点已经被提交到index,但link数为0。这个发生在当一个打开的文件被删除,然后允许了提交。正常情况下,该inode应该被删除当文件被关闭的时候。无论如何,在不干净卸载的情况下,orphans需要被记录。不干净卸载后,无论是搜寻整个index还是保持一个list在flash的某处,orphans' inodes必须被删除。
孤立区是一个固定数量的LEBs,位于LPT area和main area。孤立区LEBs的数量当文件系统创建时指定。最小数量是1。孤立区的大小可以适应在一个LEB中:(leb_size-32)/8
例如,一个15872字节的LEB可以适应1980个orphans所以一个LEB已经足够了。
orphans被累积在一个RB-tree。当节点的link数变为0,这个inode号被添加到这个RB-tree。当inode被删除,它将从tree中移除。当提交运行时,任何orphans树中新orphans被写到orphan区。如果orphan区已经满,空间将被扩大。通常会有足够大的空间的因为通常会防止用户创建多于允许的最大数目的orphans。
最后一个UBIFS区是主存储区(main area)。Main area包含由文件系统数据和index组成的节点。一个main area LEB可能是一个index eraseblock或者是一个non-index eraseblock。一个non-index eraseblock可能是一个芽或者已经被提交。一个芽可能是当前日志头中的一个。一个包含提交节点LEB仍然可以变成一个芽如果它还有剩余空间。因此一个芽LEB从日志开始的地方有一个偏移,尽管偏移通常为0。