linux 文件系统构建之初步了解yaffs

http://blog.sina.com.cn/s/blog_a20257d80101ku70.html

第一,yaffs简介

    yaffs也是一个flash文件系统,它于2001年由CharlesManning命名,当时CharlesManning创建的。现存的的flash文件系统杂乱还不能丢弃,但可以提出另一种flash文件系统,这个系统就是yaffs文件系统,yaffs文件系统适用于工作在nandflash上。

    自2001年底yaffs文件系统发布以来,yaffs已经应用到多个系统上,而2002年12月,正式用于linux文件系统。

   yaffs1是yaffs发布的第一个版本,yaffs1支持每页512字节的类似于具有闪存存储布局的nand器件;yaffs2是yaffs1的扩展,用于支持更大的,具有更多限制的器件。

   yaffs 可移植性好,可用于多种操作系统上,包括实时系统。yaffs既可用于norflash文件系统,也可用于类似于RAM文件系统。

 

第二.yaffs文件的分类与区别

 

本文提到三个术语:yaffsyaffs1 yaffs2,用哪种术语与操作模式有关:

---yaffs1指的是简单操作模式,即用于删除标记和跟踪状态

---yaffs2指的是更复杂的操作模式,用于支持那些不能删除标记的大的flash器件;

---yaffs指的是同时进行以上两种操作;

  yaffs1最初设计工作于每页具有512字节的器件,现在已经设计用于支持更大的每页具有更大容量的器件,例如每页1K字节。yaffs2最初设计工作于每页具有1K字节的器件,也可通过(in-bandtags)带内标签技术使之用于页容量更小的器件。

  yaffs1文件系统比较容易理解,因此,要了解yaffs2,最好事先了解yaffs1

 

第三.对象(object)

 

用yaffs的术语讲,object是存储在文件系统中的任何实体,例如:

 ---规则的数据文件;

 ---目录

 ---硬链接

 ---符号链接

 ---特殊的对象,例如管道,设备

 ---所有的对象都用一个独一无二的整数文件ID识别

 ---在标准的POSIX模式下,例如linux用的目录项,dentries等。

    节点是一个对象,表示下面的文件或者目录;dentry也是一个object,具有一个字符串名,一个指向节点的指针以及一个指向父dentry的指针。例如下面的树

 

   /

   |

   foo

    \

   bar bar2

   该树具有四个节点:foo、bar、bar2、和root;具有三个dentry:一个dentry链接bar到foo,另一个dentry链接bar2到foo、第三个dentry一个链接foo到root;一个节点可以是一个规则文件,一个目录或者一个特殊文件。目录提供了一种命名机制用于定位节点,在posix下,每个节点都有0个、1一个或者多个dentries。一个节点对应一个dentry这种情况很容易理解,一个节点对应多个dentries,那么就可以通过多个名字访问这个节点,这种功能可以通过硬链接实现;

   一个节点对应0个entry通常很难理解,当一个俄节点没有链接,但是仍然有一个句柄对该节点开放时就会出现一个节点对应0个entry的情况,解除文件链接删除了dentry但是节点仍然存在,只有句柄关闭了,这个节点才会被删除。

 

第四,yaffs的nand模型

 

   nand flash是以页的方式管理存储的,页是分配和编程的最小单元。在yaffs中,分配的最小单元是chunk,典型情况下,一个chunk和一个nand页是一样的,但是可以很灵活的用一个chunk映射多个页,例如一个系统有两片nand器件并行工作相当于1024个bytes的chunks即2个pages,这种特点使系统很容易配置。

   通常,一个block往往由多个chunk组成,block是擦除的最小单元,nandflash往往都带有badblock,而且一些block在使用过程中也会坏掉,这样就要求yaffs能检测badblock并标记该badblock,因此nandflash必须有一套错误检测和纠错码ECC,yaffs可以使用当前通用的ECC逻辑也可使用自己的ECC逻辑。

 

第五.yaffs存储文件的方法

 

   yaffs1有个改进的的log结构,但yaffs2有一个真实的log结构。具有真正的log结构的文件系统仅仅能按顺序进行写操作。yaffs1使用删除标记(deletionmarker),它破坏了顺序写的规则。yaffs2不使用删除标记(deletionmarker)

   当写数据时,数据并不被写入到文件中指定的位置,而是按照顺序log的方式写入到文件系统中。log中的条目是一个具有大小的chunk,有两种类型的chunk:

    ---datachunk:   具有规则数据文件内容的chunk

   ---object header:一个对象(可以是目录,规则数据文件,硬链接,软链接,特殊描述符等等)

                      描述符,例如父目录描述符,对象名描述符等。

   每个chunk都有tag与之对应,tag包含了下面重要的域:

    1. objectID:    用于的识别chunk属于那个对象;

    2. chunkID:     用于识别该chunk属于那个文件;chunkID号为0表示这个chunk包含了一

                       个object header,chunkID==1表示文件的第一个chunk,以此类推。

    3. Deletion Marker:表示这个chunk不再使用了,这个仅存在于yaffs1中。

    4.  ByteCount:   如果这个chunk是一个数据chunk,该域表示该数据的字节数。

    5.  SerialNumber:  用于区分具有相同objectID和chunkID的chunk,这是yaffs1独有的。

                        例如,加如一个nand,它的每个块具有4个chunks,且flash是空的或者擦除

                        过的,我们先来创建一个文件:

Block  chunk  objectI  chunked   DeletionComment

      500            Live    Object Header for this file (length 0)

下面,我们将要写一些数据到文件中:

Block ChunkObjectId ChunkId Deletion Comment

 

         500          Live     ObjectHeader for this file (length 0)

 

          500          Live     Firstchunk of data

 

          500          Live     Second chunk of data

 

          500          Live     Thirdchunk of data

下面,关闭文件,以上操作,为文件添加了对象首部,下面看看这个对象首部是怎么样被删除的:

Block ChunkObjectId ChunkIdDeletion      Comment

 

       500           Deleted      Object Header for this file (length 0)

 

         500           Live            First chunk of data

 

         500           Live            Second chunk of data

 

         500           Live            Third chunk of data


         500           Live             New Object Header for this file (length 0)

 现在我们打开文件进行读写操作,覆盖文件的第一个chunk然后关闭文件,被替换的数据和对象头chunk就这样被删除了。

 

Block ChunkObjectId ChunkId Deletion  Comment

          500          Deleted  Obsolete object header.

 

          500         Deleted  Obsolete first chunk of data

 

          500         Live       Second chunk of data

 

          500          Live       Third chunk of data

 

          500          Deleted  Obsolete object header

 

          500          Live       New first chunk of file

 

          500         Live       New object header

 

现在我们通过用O_TRUNC打开文件调整文件大小为0,然后关闭文件。该操作在文件中写入了一个长度为0和内容被修改过的objectheader,这就使data chunks被删除掉了。

 

Block ChunkObjectId ChunkId Deletion  Comment

         500           Deleted Obsolete object header.

 

         500           Deleted Obsolete first chunk of data

 

         500           Deleted Second chunk of data

 

         500           Deleted Third chunk of data

 

         500           Deleted Obsolete object header

 

         500           Deleted Deleted first chunk of file

 

         500           elted    Obsoleted object header

 

         500           Live      New object header (length 0).

 

请留意block1中的所有页是怎么样被删除的。删除掉block1后意味着block1不再包含有用的信息,所以现在我们可以擦除掉block1以回收空间。

下面,我们将要重新命名文件,为了重新命名文件,我们必须为文件写一个新的objectheader

 

Block ChunkObjectId ChunkId Deletion  Comment

 

                         Erased

 

                         Erased

 

                        Erased

 

                         Erased

 

         500           Deleted   Obsolete object header

 

         500          Deleted   Deleted first chunk of file

 

        500           Deleted   Obsoleted object header

 

         500           Deleted   Obsolete object header

 

         500          Live      New object header showing new name

 

    现在block2仅仅包含了已删除的chunk,可以擦除后重新利用。

   请注意tag怎么样告诉我们:哪个chunks属于哪个对象、文件中chunk的具体位置、当前的chunk是哪个。

   依据这些信息,我们可以重新创建文件的状态而不用关心NAND中chunks的替换。这就意味着如果系统在以上序列任何点发生crash,powerfail,......等,系统将会出问题,但我们仍然能恢复文件系统到那个点。

   请注意flash中并没有存储文件分配表或者类似结构,从而减少了写和擦除的数量,提高了写性能,当然也提高了系统的强壮性。文件分配表或者类似结构的崩溃在嵌入式系统中是很常见的失败,yaffs没有这种结构,这就使yaffs更加茁壮,换句话说你无法损坏你没有存储的东西。

 

第六.垃圾回收

 

   我们知道,当一个block仅仅包含删除的chunk,这个block就可以被删除并重新利用,但是在某种情况下,会出现有很多block,但是每个block中仅仅包含极少的chunk,显然我们不能擦除掉,否则就会破坏数据。我们需要作的是将有用的chunk尽可能复制到一个block中并删除掉源block,使之可以重复利用。这个过程称为垃圾回收。yaffs的垃圾回收过程如下:

   1. 根据以上的方法找到值得回收的block,没有找到就退出。

   2. 重新搜索block中的chunk,如果chunk还在使用中就创建一个新的拷贝,删除原来旧的,修正RAM数据

      结果以反眏这种变化。一旦块中的所有chunk都被删除了,就可以擦除并回收利用。

    步骤2后,当拷贝完成后,将删除掉源chunk,yaffs不会编程已删除的marker,因为无论如何他们都要被擦除。如果在我们操作的过程中掉电了,我们仍能通过查找序列号区分出源chunk和拷贝的不同,事实上两个版本都有相同的内容,无论哪一个都可以用。

   以下方法可以确定是否一个块是值得回收的:

   如果有很多已擦除的块,yaffs就不需要努力的工作,仅仅回收存在很少有用chunk的block就可以了这个称为消极的垃圾回收。

   但是如果有很少的已擦除块,yaffs将要努力工作以回收更多的空间,即便这个块里有很多chunks正在使用也会进行垃圾回收,这个术语成为积极的垃圾回收。

   如果垃圾回收是积极的,整个块将在一次垃圾回收周期中被选中。如果垃圾回收是消极的,在整个垃圾回收周期中,拷贝的数量将会减少,这样作的目的是减少垃圾回收负荷提高响应能力。

   以上方法的合理之处在于,当有可能减少需要执行的回收次数时,这种方法延迟了垃圾回收,从而增加了系统的平均性能。这个其实与扩大垃圾回收以防止导致文件系统波动的目标是冲突的,这种冲突的目标使垃圾回收变的特别有挑战性。

   所有的flash文件系统都需要一种垃圾回收方法用于重新利用空间资源,垃圾回收同系统性能是一个flash文件系统性能的决定性因素。

   许多文件系统不得不在单一回收sweep中作大量的努力。yaffs一次仅仅处理一个块以限制了垃圾回收中的负荷,这样减少了系统stall的时间。yaffs垃圾回收算法考虑了减少stall时间和增加吞吐量的目标。

 

第七.yaffs1序列号

 

   在yaffs1中每个chunk都用一个2bit的序列号标志,这个序列号依次增加。具有相同tag值的chunk可以相互替代,例如当一个chunk被重写了或者源chunk已经被垃圾回程系统拷贝了。

   现在我们首先假设,我们不知道序列号,如果我们要用一个有新object header的chunk取代有旧的object header的chunk并且重新命名,yaffs将要作如下操作:

   1.删除旧的chunk;

   2.写新的chunk;

   如果删除旧的chunk后系统失效了,系统可以没有匹配相关tags值的chunk方式结束,这样会导致文件丢失(endup inlost+found)。这样一来,我们就必须在删除一个旧的chunk之前先写一个新的chunk,但是如果这样做将会发生什么呢?

   写新chunk

   删除旧chunk

如果系统在我们写完新chunk后失效了,那么我们将会有tag匹配的两个chunk。该怎么区分那个chunk是当前的?通过检查序列号可以确定那个是当前chunk,因为序列号在旧的chunk删除之前是逐次递增的,一个2bit的序列号已经足够了。有效的配对是:

Old                    New

 

00                     01

 

01                     10

 

10                     11

 

11                     00

这样通过检查附给新旧tag的序列号,就能确定当前chunk,删除旧chunk。

 

第八.yaffs2 NAND模式

 

    yaffs2是yaffs1的扩展,设计工作于新型NAND上,包括:

    1. 0复写,yaffs1仅仅在chunk空余区域复写一个字节就可以设置好删除标记。新的NAND器件不

        能承受重复写操作,yaffs2根本不需要重复写操作。

    2. block内顺序chunk写操作,更新型的NAND可靠性规范趋向于确保顺序写操作。因为yaffs2  

        不能写入删除标记,因此block内的chunk写操作必须是严格的顺序操作。

    因为yaffs2很少执行写操作,因此yaffs2有提高写性能的潜力。

    yaffs2不能使用删除标记,因为这将会与0重复写操作强制规则冲突,因此必须提供另外一种机制用于决定那个是当前chunk,那个是删除的chunk,这种机制就是:

 ---序列号:每个块都会分配一个序列号,文件系统的序列号是一次增加的,块中的每个chunk都用序列号

    标记。序列号提供了按时间顺序组织log的一种方法。

 ---缩减的头部标志:缩减的头部标志用于标识缩减数据文件大小的对象头部,这里必须注意yaffs1的序列

    号和yaffs2的序列号是不一样的。

 

   这里我们仍然要提到yaffs2中chunk的删除。chunk在RAM数据结构中被垃圾回收程序标记为删除,但是没有删除标记写入flash。

   按时间顺序的序列号允许yaffs确定事件的顺序以及回复文件系统装载,该目的可以通过按时间顺序向后扫描实现。例如:从最高的序列号向后扫描到最低的序列号。这样:因为我们是向后扫描,最近写的是当前chunk,在向后扫描的过程中必然首先遇到objectId:chunkId对匹配的chunk,所有后续的objectId:chunkId对匹配的chunk必须是独一无二的,且被认为已经删除。这句话的意思是说,如果遇到两个或者多个chunk,他们的objectId:chunkId都一样,那么除过当前的chunk,其它的chunk应该认为已经被删除了。

对象头中的文件长度用于消减已调整过大小的文件。如果发现了一个对象头,然后后续的数据chunk的长度超过了chunk的长度超过了文件的长度,那么这个数据chunk明显是废弃掉的,应该认为该chunk被删除了。请注意,当前文件头和废弃的对象文件头大小都可用于文件的重建。

   缩减的头标记是有点复杂的,使用缩减的头标记的母的是用于指示这个对象头表明一个文件的大小已经改变了,同时为了防止这个对象头被垃圾回收程序删除。

   要理解这些内容,可以考虑以下如果缩减的头标记不存在会发生什么情况。

为了简单起见,假设在这过程中没有垃圾回收,事实上是可以这样假设的,因为这并不改变逻辑。假设有这样一个文件,已经执行了如下操作:

 

h =open(“foo”,O_CREAT| O_RDWR, S_IREAD|S_IWRITE);

write(h,data,51024*1024);

lseek(h,2*1024*1024,SEEK_SET);

write(h,data,1024*1024);

close(h);3

 

   现在文件长度为3M,但是有一个“hole“在1M和2M之间。根据posix,“hole“应该总是读为0,yaffs2将要在NAND中用下列的一系列chunk取代它:

   ---创建文件对象头,文件长度为0

   ---0-5MB的数据chunk

   ---用于truncation(文件长度为1M)的object header,

   ---1M的数据chunk(2MB 到3MB)

   ---object header关闭(文件长度为3MB)

到这个阶段,仅仅下面的数据chunk是最新的

步骤2中产生的第一个1MB的数据

步骤4中产生的1MB的数据

步骤5中产生的objectheader

 

   但是yaffs必须记住第三步的truncation,否则yaffs就会忘记文件中的那个“hole",这将会导致旧的数据又可以重新可见,这样的话,如果yaffs产生了一个hole。

那么它必须写一个缩减的头部用于只是hole的起始位置和一个规则的对象头用于标记hole的结束位置。缩减的头部改变了垃圾回收程序的行为,,以确保这些缩减的头部不被擦除一直到可以安全的擦除为止。但这样对于系统中的垃圾回收程序也有一个消极的影响,会导致有hole的文件的大量使用。缩减的头部用于指示文件已经被删除了,文件删除的记录是不能丢失的。

  yaffs2的extendedtags,yaffs2使用额外的域扩展了objectheader中的tags用于提高挂载扫描性能,后边会详细介绍扩展tags的操作。

 

第九.NAND坏block处理,错误处理

 

   NADN flash被设计成为具有低成本高密度的存储器件。为了使NANDflash的尺寸尽可能小,以降低成本,NANDflash允许有一定的坏块,甚至一些块在使用的过程中也会坏掉,因此任何没有有效的坏块处理机制的文件系统是不适合用于NAND法拉盛器件的。 yaffs1是使用Smart-Mediastyle的方式进行坏块标记。这种方法检查sparearea的第六个字节(byte5)。在一个好的块中,这个字节读出来应该是0xff,坏块出场时标记为0X00.如果yaffs1确定一个块坏掉了,那么将会使用0X59(“Y")标记这个坏块。用一个不同的标记允许yaffs标记坏块以区别于出厂坏块。yaffs2模式设计用来支持一个更宽范围的器件和spare-area布局。这样yaffs2就不用决定那个字节要标记而是调用驱动函数以确定是否这个块是坏的,是否将其标记为坏块。

   那么,yaffs什么时候标记一个坏块呢?如果读写操作失败或者检测到三个ECC错误yaffs将会标记一个坏块。一个块如果被标记为坏块,那么这个块将不再使用,从而提高了文件系统的强壮性。这种机制适合于SLCflash,MLC机制正在开发中。而且NANDflashcell也会由于NAND操作和漏电而损坏。这些错误可以通过ECC进行纠正。ECC可以用于硬件,软件驱动,或者yaffs。任何缺乏有效ECC处理的文件系统都不适合用于NANDflash

   yaffs1模式既可以嵌入ECC也可使用驱动或者硬件提供的ECC。因为yaffs2模式设计用于更广范围的器件。它自己没有提供ECC,但是驱动可以提供ECC。

   yaffs提供的ECC代码是最快的SmartMedia应用C代码,该ECC兼容我们熟知的ECC算法。该代码能纠正256字节数据块中任何一个单一bit错误,每256个字节的数据块中可以检测出2个错误。这是一种特别有效的纠错方法,这种方法为SLC类型的NANDflash提供非常高的可靠性。要了解完整的错误处理流程,请参阅yaffs error mitigationdocument

 

第十.RAM结构

 

   理论上来说可以使用占用很少RAM结构的日志结构文件系统,但这种文件系统降低了系统的性能。这样,重要的RAM数据结构需要存储足够多的信息以提供足够高的性能。那么RAM接哦故是为什么目标服务的?RAM结构定义在yaffs_guts.h中,RAM结构的目的是:

   设备/分区:这个称为yaffs_dev.要了解这些需要实现掌握yaffs分区或者挂在点的相关信息。用这些而不使用global允许yaffs自动的支持多个分区和不同的分区类型。事实上,这里提到的数据结构都是RAM数据结构的一部分或者要通过RAM数据结构才能被访问。

   NAND 块信息:这个命名为yaffs_block_info而且需要掌握系统中每个NAND块的当前状态。每个yaffs_Device都有一组yaffs_block。

   NAND chunk信息:这是与yaffs_device相关的位域该位域包含了系统中每个chunk的当前应用状态。分区中每个chunk有一个bit。

 

   object:这个称为yaffs_obj,它包含了一个树形结构,它是一种在文件中查找数据chunk的机制。这个树形结构包含了文件系统中对象的状态,一个对象就是一个规则文件,目录,硬链接,符号链接或者专用链接。有多种yaffs_Objects用于反眏不同的object类型需要的不同数据。每个object都使用objectId唯一识别。

文件结构:对于每一个文件对象,yaffs包含一个tree,这个tree提供了查找文件中数据chunk的机制。这个tree包含了称为yaffs_tnode的节点(treenodes)。

   目录结构:目录结构允许通过名称查找object。这个目录结构采用一个双向链表构建,存在于目录中,将一个目录siblingobject链接在一起。

   Object number hash table: The object number hash table提供了用objectId查找object的机制,hashed被散列化用于选择一个hashbucket,每个hash bucket都有一个属于这个hashbucket的对象的双向链表,以及bucket中对象的数量。

   Cache:yaffs提供了读写缓存,这对于提高短时操作的性能具有重大意义。这个cache的大小可在运行时设置。下面将会更详细的介绍这些数据结构。

   yaffs大量使用了双向链表。因为它提供了快速的插入、删除、遍历等操作,因而这些数据结构是非常有用的。它需要用到两个指针而不是一个。

 

10.1yaffs 对象

 

文件中的每个object都可使用一个yaffs_obj表示。yaffs_obj的主要功能是大多数object的metadata和类型说明信息。这意味着几乎一个object的所有信息都可以立即被访问而不用去读取flash。这些元数据包括:

      objectId:     该数字用于识别 object.

      parent:       指向parent directory的指针. 不是所有的object都有一个parent,根目录以及

                     无链接文件和已删除文件没有parent,因此,它的值为NULL。

      Short name:   如果文件名足够短可以写入到一个小的固定大小的数组中(缺省为16个字符)那么

                     它将被存储在这里,否则当需要它时,必须每次从flash中取出。


  type:          object类型

  权限, 时间,所有者和其它属性依赖于object类型,

yaffs_obj还存储了如下信息:

      Atnode tree,file extents etc for data files

      A directory chain for directories

      An equivalent object for hard links

      A string for symbolic links

 

10.2 Look-upby object id

 

    这个机制的目的是通过deviceId和objectId快速访问对象。在垃圾回收、扫描、和类似操作过程中这个是必要的。为了实现这个目的,每个yaffs_dev都使用了一个hash表,该hash表有256个buckets(tunable),每个hash表都包含bucket中的yaffs_objs的一个双向链表,hash表的hash功能仅仅用来屏蔽objectId的最低有效位。yaffs在分配objectId时,尽可能的使hashbucket中的对象号码比较小,以防止hash条目变的太长。这个机制也被分配了一个新的独一无二的objectId.

 

10.3Directory structure

 

    采用目录结构的目的是通过名称快速访问对象,这需执行文件操作例如打开,重命名等

yaffs目录结构由YAFFS_OBJECT_TYPE_DIRECTORY类型的对象树构成.这些对象都有一个双向链接的children节点。每个yaffs_obj都有一个双向链接节点,称为sibling他将目录中的sibling链接在一起。每个yaffs分区都有一个根目录。

    每个yaffs分区都有一些特殊目的“fake"目录不存储在NAND中,由挂载产生。

    lost+found:这个目录用于存放丢失的文件块,不能被正常的directorytree取代

    链接断开和删除:pseudo-directories不存在于directory tree中,把object放在这些目录中,表示这些object的链接已经断开或者object已被删除。

    每个object都有一个名字,用于在目录中查找object,因而目录中的每个名字都是唯一的。这个规则不适合于链接断开和已删除的pseudo-directories,因为我们从不会用名字去查找链接断开或者已经删除的文件,也许会有很多名称相同的已删除文件。这一项很常见,无论是linux还是windows,他们的回收站里是可以存放同名文件的。

    采用objectname查找object的方案可以采用两种机制进行加速:

    短文件名直接存储在yaffs_obj中,这使他们不必从flash中载入。

    每个object都有一个“namesum“,这是一个简单的名称hash值,可以加快匹配过程,因为对比hash值比对比整个名称字符串效率更高

           linux <wbr>文件系统构建之初步了解yaffs

 以上的目录结构代表了以下目录树

 

             Root directory

/a             Directory containing c,d,e

/b             Empty directory

/lost+found    Empty directory

/a/c               Directory

/a/d               Data file

/a/e               Data file

 

   如果一个yaffs分区有些坏block,那么就有可能出现这样的情况,一个object在scanning过程中被创建了,但是找不到objectheader(包含了objectname)。这个object将没有名称,这种情况下,yaffs采用objnnnn形式的对象号创建一个pseudiym,以使该object可以放置于目录结构中。

 

10.4硬链接

 

    在POSIX文件系统中, object (inodes in Linux terminology, vnode inBSD-speak) 和object name(dentry in Linuxterminology)是独立的在于一个object,可以有0个、一个、或者更多的objectname

有0个链接的文件是由于采用了如下的创建方法:

h =open(“foo”,O_CREAT | O_RDWR, S_IREAD | S_IWRITE);

unlink(“foo”); 

read(h,buffer,100);

write(h,buffer,100);

close(h);

    yaffs不存储独立的文件和名字记录,因为这很浪费空间并且会引起额外的读写操作。相反yaffs对仅有一个名字典型object的情况进行了优化。也就是说,yaffs为每个yaffs_obj存储一个名字,采用“cheats"完成其它操作。那些有0个名字的对象(例如无连接的对象)通过重新命名对象的方法进行处理,这些对象将被放入fake目录,这个目录用于存储无链接的对象。具有多个名称的对象可通过指定其中一个对象为main_object和取得多个引用它的硬链接来处理该对象。

 

     节点号(vindin BSD-speak)是每个文件系统的唯一号,用于识别object。yaffs主对象的object id是要显示给用户的。也就是说,如果通过一个硬链接object查询节点号,那么将会返回等价对象的objectid。

 

在 hardlink pseudo-objects和他们的等价object间的链接是凭借两个域:

每个objects都有一个双向的硬链接链表节点,该节点将硬链接链表链接到objects上。

每个硬链接都有一个指向等价objects的指针。

这个结构的用法是直接的,除过一个有趣的案例:当硬链接被删除后。考虑一下下列事件的结果:

     # touch a      Create file a

     # ln a b           Create a hard link b with equivalent object a.

     # rm a         Remove a. The object must still exist under the name b

   在步骤3中,yaffs不能仅仅删除等价的对象,因为那将导致留下一个“hanging“硬链接且该硬链接没有等价的对象。同样,我们也不能仅仅改变硬链接对象中其中一个类型,因为那将引起对象的objectid发生变化。这是不能接受的,因为它将破坏任何一种使用 objectid的机制,这样一来,如果一个具有硬链接的对象被删除了,,我们还应该作如下工作:

   选择该对象其中一个硬链接。

   采用硬链接中的名字重命名该objects

   删除hard link pseudo-object.

 

10.5符号链接和特殊objects

 

   这些objects类型仅仅存储了一些值,这些值是完整的POSIX文件系统所需要的。符号链接objects存储符号链接字符串,用于提供POSIX符号字符串机制。特殊的objects用于存储设备号,管道、以及其它类似特殊objects。

 

10.6文件objects

 

   yaffs_Objects有一个类型和一个变体部分,该变体部分是不同数据的联合,这些数据用于保持不同objects类型的状态。文件objects有类型YAFFS_OBJECT_TYPE_FILE,和一个联合的文件变量。他们存储了如下主要值:

   fileSize: file size

   topLevel: depth of the Tnode tree

   top: pointer to top of Tnode tree

   文件大小的含义是显而易见的。这是当前文件的长度。一个文件也许有“hole“这是由向前查询文件超出文件内容引起的。

   顶层和顶部协同工作以控制 Tnode trees

 

10.6.1 Tnodetree

 

   每一个文件都有一个 Tnode trees,用以提供从文件到实际NANDchunk地址间的映射。top是一个指向

tnode tree顶部的一个指针。

 linux <wbr>文件系统构建之初步了解yaffs

    tnode tree由tnode(treenode)组成,这些tnode按等级组织,每一个节点都具有:

---在0级以上的其它层级上,一个tnode有8个指针指向下级的其它tnode。

---在0层级上,一个tnode有16个NANDchunk Ids用于识别chunk在RAM中多大位置。这些数是2的幂,可以通过

   位屏蔽的方法查表,遍历tnodetree的代码在yaffs_FindLevel0Tnode()函数中。

   当文件的大小增长时,更多的tnode加入到tnodetree中,当当前层级满后,更多的层级将会加入。如果一个文件减小了,或者尺寸变小了,那么tnodetree就应该被修剪。如果文件中的一个chunk被替代(例如:数据复写,或者被垃圾回收程序拷贝)了那么tnodetree 必须更新以反应这种变化。tnode提供了大量的yaffs的 RAM用法。

 

11.多种机制如何工作

 

看完yaffs的数据结构后,思考以下yaffs中的各种机制是如何运行的。

 

11.1Block 和chunk 管理

 

11.1.1Block 状态

 

yaffs跟踪分区中每个block和chunk的状态。这些信息首先在scan的过程中或者从一个checkpoint恢复的过程中建立。一个块处于下列状态(见yaffs_BlockState定义)之一:

 

UNKNOWN                block的状态是未知的.

 

NEEDS_SCANNING         在pre-scanning过程中确定该快上有重要信息需要scanning

 

SCANNING               该块当前正被scanning.

 

EMPTY                  该块已被擦除可以用于重新分配,通过擦除一个block可以使这                       个block进入该状态。

ALLOCATING             该块当前正被chunkallocator分配。

FULL                   这个block中的所有chunk应被分配掉了,但至少有一个chunk包含了有用的信息还

                        没有被删除掉

DIRTY                  该block中的所有chunk都已经被分配掉并被删除掉了,在此之前该block也许出于

                       FULL状态或者COLLECTING状态,现在该block可以被擦除掉返回到EMPTY状态 

 

CHECKPOINT             该块含有checkpoint data 。当checkpoint无效时,该block可以擦除掉并返回

                       到ENPTY状态。

 

COLLECTING             该块已被垃圾回收程序回收.主要所有livedata都被检查完那么该块将进入 DIRTY

                       状态。

 

DEAD                   该块已经被丢弃不用或者标记为bad。这是块的终点状态。

 

linux <wbr>文件系统构建之初步了解yaffs

 

正常的运行状态有:EMPTY, ALLOCATING, FULL, COLLECTING 和DIRTY.

 

checkpoint状态用于checkpoint块.

 

  DEAD 状态是块的terminal状态在该状态下由于该块或者被标记为坏块或者检测到存在错误,将不再继续使用。

 

11.1.2Block 和chunk 分配

 

   一个chunk在写入数据之前,必须被分配。chunk分配机制由yaffs_AllocateChunk()提供,他是非常简单的。每个分区都有一个当前块,他是从分区中分配的。这个过程术语称为allocationblock。chunk按顺序从已分配的块中分配。当chunk分配后,block和chunk的管理信息被更新,包括block的pagesInUse计数器和chunk使用的bitmap。当一个块完全被分配周,另外一个空的块将会被选择成为已分配块。块的选择采用从上一个已分配块向前搜索的方法进行选择。

    在yaffs_guts.cV1.99版本之前,yaffs扫描将识别处正在使用的已分配块,当文件系统最后被关闭了,那么此后将会从该点继续分配块。这样做有一个好处,可以继续使用同一个block稍微减少了垃圾的产生。然而作这些工作可能导致问题:例如当尝试写一个正在写的块时电源掉电了。这样yaffs总会从挂载后的新块开始分配。这将增加垃圾的数量,但是却提高了系统的健壮性。

 

11.1.3关于wearleveling

 

   因为flash存储块仅仅能承受有限数量的写和擦除(术语称为flashendurance),flash文件系统要确保尽可能减少对同一块的持续写操作。Wearleveling在日志结构的文件系统(例如yaffs)中是很少被考虑的,因为总是从log的末尾去写,这样可以确保不总写同一个块。有两种方法满足wearleveling:

用一套专用的写操作函数执行wearleveling;

   允许一定程度的wear leveling发生将其作为其它活动的副作用。

   yaffs采用了第二种方法.首先作为log结构,yaffs通过顺序写操作本质上散开了wear。这就意味着我们不必单独指定一些块用于特定目的,从而不会出现对这些块的写操作多于其它块。第二,块按顺序从分区中已擦除块中进行分配。这样擦除过程(回收垃圾删除后)和块分配倾向于散开块的应用并提供了一定的wear leveling。这样及时没有代码用于执行 wear leveling它也会作为其它活动的副作用而发生。这种方案在实际中运行的很好。

 

11.1.4yaffs_tnode 和yaffs_obj 结构管理

 

   yaffs_tnodes 和yaffs_objs当需要时动态的产生和释放。可通过使用标准的内存分配函数进行分配和管理,例如使用malloc,kmalloc等等,但是这将会导致性能问题,特别是在倾向于使用简单的内存分配方法的嵌入式系统中,许多小的结构的分配和删除可能引起内存碎片的产生,从而引起内存耗尽和系统速度降低等问题。为了防止这些问题,yaffs内部管理这些object。yaffs一次创建许多结构(100或者更多)然后从一个应用链表中逐个的分配他们。当结构释放后,他们将被挂在未使用的链表中以重新利用。

 

11.2内部缓存

 

   该节表述了yaffs内部缓存应用的标准。替代的缓存已经被设计用于其它目的,在不久的将来,yaffs也许会被扩展以支持可插入的缓存策略。

   采用内部缓存的目的是减少对NAND的访问,极大的降低许多小的读写操作。无论你相信或者不相信,有一些应用会因此读或者写很少的字节,例如:

while(...)

write(h,buf,1);

   如果每一次读写操作都导致对flash的写操作,那么这类操作对于性能和flash存储器的生命的影响是可怕的。yaffs提供了内部缓存用于降低这类操作的次数。

   yaffs的内部缓存称为“ShortOp Cache” (short operationcache),他的提出仅仅用于处理chunk不对齐的操作。页对齐的读写操作将绕过该缓存除非chunk已经位于该缓存中。

   缓存存储于缓存条目的矩阵中。缓存条目的数量在yafs_Device配置中通过dev->nShortOpCaches进行设置。缓存在初始化时进行配置。将缓存大小设置为0将不使能该缓存。每个缓存条目都包含如下内容:

   object id, chunk id, nbytes (the tag info of the chunk beingcached)

   The actual data bytes

   Cache slot status (last used counter, dirty,…)

   查找一个未用的缓冲记录是简单的操作,我们只需要在缓冲记录中反复查找,找到一个不忙的就行。如果所有的缓冲记录都忙,那么将要执行一个缓冲push-out操作然后尝试重新查找,如果访问了一个缓冲记录,那么也必须使last-usecounter加1.这个计数值这个计数值在theleast-recently-used (LRU) algorithm中使用。

已经修改过的缓冲记录和还未写回到flash中的缓冲记录需使用dirty标识进行标记。它告诉我们是否需要在一次flush过程中或者push-out过程中写完数据。

   当发生push-out事件时,无论缓冲区是否是满的我们都需要将一条记录push出缓冲区以便腾出空间装载新记录。yaffs用LRU算法选择LRU缓冲记录然后按升序为该文件写完所有的dirty记录并将其标记为free。

   无论什么时候一个文件被刷新了(file flush或者close),文件的dirty记录都要被写完。在确定的时间(例如在unmount前或者sync())整个缓冲都要刷新。

   缓冲策略采用非常简单的算法(例如:goodbang-for-bucks)提高了系统性能。

   除正常的缓冲功能外,shortOpCache也为inband tags支持作了大量的迁移,因为所有的inbandtag访问都不是chunk对齐的且必须重新对齐。缓冲区处理这个问题很容易。

 

11.3Scanning

 

   scanning是一个过程,在这过程中,文件系统状态从scratch重建。如果没有可用的checkpoint数据或者如果挂载程序忽略了checkpoint数据,在挂在一个yaffs分区时这种情况就会发生。

   Scanning涉及到从系统中所有的activechunk读tags活动,因此将会花费一定的时间,但或许花费的时间比你期望的少。因为前边提到yaffs1和yaffs2之间存在不同之处,因此yaffs1和yaffs2的scanning过程是不一样的,

 

11.3.1 yaffs1scanning

 

   yaffs1 scanning是相当的直接因为yaffs1使用删除标记,我们真正需要作的工作是读出分区中所有的tags并确定是否chunk是active的。读的顺序并不重要,block可以使用任何顺序读。如果删除标记还没有被设置那么这个chunk就是active的,我们需要采用乳腺癌步骤将chunk加入到文件系统中:

   如果chunk是一个数据chunk(chunkId>0)那么该chunk必须将给相关文件的tnode树。如果对象不存在,呢吗我们必须在lost+found中创建它,这个时候该对象还没有名字和其它文件描述。

   如果chunk是一个object header (chunkId ==0),那么该对项必须被创建(或者该对项已经存在于lost+found中,我们需要修改他的描述,并把它放置在目录树中正确的位置。

   在scanning的任何时间,如果我们发现我们正在读到一个chunk,它的objectId:chunkId于我们先前遇到的chunk的objectId:chunkId完全相同,那么就需要采用2bit的序列号解决争议。

   一旦scanning结束,我们还需要作修订如下时间:

---Hardlinks必须正确链接(scanning结束后,作这些事情效率会比较高)

---同样,那些被标记为无连接的对象必须被删除掉,因为这些对象将不再有句柄可以打开他们。

 

11.3.2 yaffs2scanning

  yaffs2没有删除标记,这使得scanning变的相当复杂,首先需要作的是pre-scan获取块的序列号。然后将块列表和块序列号分类组织成为按照时间的先后顺序的链表,然后yaffs从后向前遍历这些块(即以时间先后顺序的反序)。这样首先遇到的objectId:chunkId对将是当前chunk而后续的具有这些标签的chunk是过期chunk。我们必须读出块中每一个chunk的tags。

  如果该chunk是一个数据chunk(chunkId >0)那么:

  具有已经查找到的具有相同objectId:chunkId的chunk必不是当前chunk,删除掉这个chunk。

  否则,如果object标记为删除,遭到破坏,那么删除掉它。

  否则如果object不存在,那么这个chunk将被写,因为先前以对这个对象的objectheader进行了写操作,因此这个chunk一定是文件中最后一个被写的chunk,而且当这个chunk关闭了,但是所在的文件仍然打开着时这种情况一定会发生。我们能创建文件并用chunkId和nbyte设置文件扩展。否则如果对象存在而chunk所在的位置超出了对象的文件的扫描大小(extents)这个chunk就是一个truncatedchunk 应该被删除掉。否则它就应该放置进入文件的tnode tree

  如果该chunk是一个object header 即 chunkId== 0,那么:

  如果object处于删除目录中,那么就应该将该object标记为已删除并设置object的shrinksize为0。否则如果没有找到该object的object header,那么这是一个最新objectheader并拥有最新的名称。如果没有发现该object的数据chunk那么objectheader中的size也是一个文件扩展。文件size用于设置文件scansize,因为文件被写时,文件没有超过那个点,我们可以放心的忽略掉后面超过那个点的数据chunk。否则如果文件size比当前文件的scansize小那么就用file size作为scansize。否则这就是一个废弃的 object header已经被删除了。

 

11.4Checkpoint

 

   挂载scanning需要耗费相当多的的时间,虽然挂载很慢但是也许比你想象的快。checkpointing是一种加快挂载速度的机制,这种机制记录了yaffs在卸载或者sync()运行时状态的一个快照,然后重建在再次挂载时重构运行时状态,这样使的挂载速度得以提高。

   实际的checkpoint机制是相当简单的,要被写入到标记为包含checkpoint数据和运行时状态的块中的数据stream将会被写到stream中。

   不是所有的状态都会被存储,仅仅用于重构运行时数据结构的状态才会被存储。例如metadata不需要被存储,因为它很容易通过lazy-loading装载。

 

checkpointstores 按照如下顺序存储数据:

 

   Start marker, including checkpoint format id.

 

   yaffs_Device information

 

   Block information

 

   Chunk flags

 

   Objects, including file structure

 

   End marker

 

   Checksum

 

checkpoint的有效性通过如下机制得以确保:

 

   The data is stored using whatever ECC mechanisms are inplace.

 

   The start marker includes a checkpoint version so that an obsoletecheckpoint will not be read if the checkpoint codechanges.

 

   Data is written to the stream as structured records, each with atype and size.

 

   The end marker must be read when expected.

 

   Two checksums are maintained across the whole checkpoint dataset.

   如果检查失败,那么checkpoint将被忽略状态将重新扫描。

但是存在一种情况,当我们正在写一个checkpoint时,block的状态也许会发生变化,因此,我们该如何正确的重构块状态呢?这可以通过以下方法达到:

   当我们写checkpoint时,checkpoint block分配选择EMPTY块。当我们写checkpoint时我们不改变其状态,因此写入到checkpoint的状态将会是EMPTY或者CHECKPOINT,这是无关紧要的。

   在checkpoint读结束后,我们已经知道那些block用于存储checkpoint数据。我们必须更新这些block以反映CHECKPOINT状态。

   修改(写、擦除等)过的checkpoint是无效的,这样可以通过任何修改路径来检查是否有checkpoint并擦除掉它。

   规范的yaffs块分配和垃圾回收逻辑必须意识到checkpoint的近似大小以确保有足够的空间用于保存checkpoint。无论对象号怎么变化,该近似值都可通过计算进行更新。

 

11.5 Extendedtags and packed tags

 

   Extended tags是一种采用抽象方式在标签中存储更多信息的方法。考虑到快速的scan时间,tag包用于存储extended tag。

   当我们scanning时,我们需要建立文件系统的结构,换句话说我们需要重建目录、tnodetree、文件名、权限等等这些可lazyloaded对象间的关系。执行这些操作都需要存储额外的信息,例如objectheaders标签中的父目录,对象类型,但是我们怎么样才能在不增大tag大小的前提条件小做到这些呢?

   Object headers总是有一个值为0的chunkId和无意义的nbytes,因为它没有数据chunk。通过使用一个bit来识别objectheaders ,我们能忽然间开辟出许多空间,这个空间我们可以用来存储有用数据。

 

11.6 Blocksummaries

 

   scanniong需要每一个待读chunk的tag,以使所有的chunk可被检索,从每一个chunk中读取tag是特别耗时的。这样blocksummaries 在2011年被加进来了。Blocksummaries将块中所有chunk的标签数据写入块中很少的chunk中(典型情况下只占用一个chunk).这允许块中chunk的所有tag在读出时能一次命中。这依赖于系统的吞吐量,这个方法可及大的降低系统的挂在时间。如果一个块的blocksummary是不可用的,那么就只有采用老的机制scan每个chunk的tag。

 

11.7 Inbandtags

 

   到目前为止,我们已经考虑了在spare area或者带外area中有足够未分配字节的chunk,这些区域用来存储tag。这意味着页的数据部分在大小上是2的幂,2的幂处理起来会很快,很多操作系统内部都有2的幂运算。

但是如果在sparearea中没有足够空间存储tag将会发生什么?采用inbandtag解决该问题,有了inbandtag,我们将这些tag同flash中数据区域存储在一起。这种方法使我们在yaffs支持的存储类型上有了更大的灵活性,但这也破坏了2的幂的对齐规则。使用inbandtag 有三个主要害处:

   由于没有了chunk的对齐规则,所有的处理必须通过shortOpCache,这需要额外的memcpy(). 

   chunk的偏移,剩余空间,和其它计算将不再仅仅进行移位和屏蔽操作,而是需要进行乘方和出发运算,这是相当的耗时的。

   在scanning的过程中读tag涉及到读整个页,这将使读变的很慢,可通过checkpointing将这种消极影响降到最低。

   这样,当规则tag不能工作时,使用inbandtag是唯一的最好的方法。

 

11.8 softdeletion

 

   软删除是一种在yaffs1模式下加速文件删除速度的机制,删除一个文件涉及到在文件中每个数据chunk中写入一个删除标记。对于大的文件该操作将会花费相当大的时间。文件系统中加入这种软删除机制就是为了避免写所有这些删除标记。软删除机制不写删除标记而是跟踪属于删除文件中的每个block中的chunk号,将其清理干净后轨道垃圾回收器中。

   当垃圾回收器看到一个块时,他就会检查当前chunk(也就是说chunk还没有被删除)是否属于已删除文件而不必拷贝这些chunk,就这样高效的删除了chunk。一旦于已删除文件相关的所有shunk都被垃圾回收器清除了,那么headerobject也可以被删除了。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值