JFFS2 文件系统的工作原理
2010-11-06 20:39:26| 分类: Linux file syste|字号 订阅
1. 操作实现
当进行写入操作时,在块还未被填满之前,仍然按顺序进行写操作,系统从 freelist 取得一个新块,而且从新块的开始部分不断地进行写操作,一旦 freelist 大小不够时,系统将会触发 “碎片收集”功能回收废弃节点。
在介质上的每个 inode 节点都有一个 jffs2_inode_cache 结构用于存储其 inode 号、inode 当前链接数和指向 inode 的物理节点链接列表开始的指针,该结构体的定义如下:
struct jffs2_inode_cache{
struct jffs2_scan_info *scan; //在扫描链表的时候存放临时信息,在扫描结束以后设置成NULL
struct jffs2_inode_cache *next;
struct jffs2_raw_node_ref *node;
_u32 ino;
int nlink;
};
这些结构存储在一个Hash 表上,每一个 Hash 表包括一个链接列表。Hash 表的操作相当简单,其 inode 号是以 Hash 表长度为模来获取它在 Hash 表中的位置。每个 Flash 数据是实体在 Flash 分区上的位置、长度都由内核数据结构 jffs2_raw_node_ref 描述。它的定义如下:
struct jfffs2_raw_node_ref {
struct jffs2_raw_node_ref *next_in_ino;
struct jffs2_raw_node_ref next_phys;
_u32 flash_offset;
_u32 totlen;
};
当 mount 操作时,系统会为节点建立映射表,但是这个映射表并不全部存放在内存里面,存放在内存中的节点信息是一个缩小尺寸的 jffs2_raw_inode 结构体,即 struct jffs2_raw_node_ref 结构体。
其中 flash_offset 表示相应数据实体在 Flash 分区上的物理地址,totlen 为包括后继数据的总长度。同一个文件的多个 jffs2_raw_node_ref 由 next_in_ino 组成一个循环链表,链表首为文件的 jffs2_inode_cache 数据结构的 node 域,链表末尾元素的 next_in_ino 则指向 jffs2_inode_cache,这样任何一个 jffs2_raw_node_ref 元素就都知道自己所在的文件了。
每个节点包含两个指向具有自身结构特点的指针变量,一个指向物理相邻的块,另一个指向 inode 链表的下一节点。用于存储这个链表最后节点的 jffs2_inode_cache 结构类型节点,其 scan 域设置为 NULL,而 nodes 域指针指向链表的第一个节点。
struct jffs2_sb_info 是一个控制整个文件系统的数据结构,它存放文件系统对 Flash 设备的块利用信息(包括块使用情况、块队列指针等)和碎片收集状态信息等。
系统中使用结构 jffs2_sb_info 来管理所有的节点链表和内存块,这个结构相当于 Linux 中的 Supper Block 超级块。
当某个 jffs2_raw_node_ref 型节点无用时,系统将通过 jffs2_mark_mode_obsolete() 函数对其 flash_offset 域标记为废弃标志,并修改相应 jffs2_sb_info 结构与 jffs2_eraseblock 结构变量中的 used_size 和 dirty_size 大小。然后,把这个被废弃的节点从 clean_list 移到 dirty_list 中。
在正常运行期间,inode 号通过文件系统的 read_inode() 函数进行操作,用合适的信息填充 struct inode。JFFS2 利用 inode 号在 hash 表上查找合适的 jffs2_inode_cache 结构,然后使用节点链表之间读取重要 inode 的每个节点,从而,建立inode 数据区域在物理位置上的一个完整映射 (map)。一旦用这种方式填充了所有的 inode 结构,它会保留在内存中直到内核内存不够的情况下裁剪 jffs2_inode_cache 为止,对应的额外信息也会被释放,剩下的只有 jffs2_raw_node_ref 节点和JFFS2 中最小限度的 jffs2_node_cache 结构初始化形式。
2. 垃圾收集
在JFFS1中,文件系统类似于队列,每一个队列都存在唯一的头指针和尾指针。最先写入日志的节点作为头,而每次写入一个新节点时,这个节点作为日志的尾。每个节点存在一个 version 节点,它专门用来存放节点的版本号,它与节点写入的顺序有关。每写入一个节点其版本号加1。节点写入总是从日志的尾进行,而读写点则没有任何限制。但是擦除和碎片收集操作总是在头处进行。如果用户请求写操作时发现存储介质上没有足够的空余空间,也就表明空余空间已经符合“碎片收集”的启动条件。如果有垃圾空间能够被回收,碎片收集进程启动将收集垃圾空间中的垃圾块,否则,碎片收集线程处于睡眠状态。
JFFS2 的碎片收集技术与JFFS1 有很多类似的地方,但 JFFS2 对JFFS1 的碎片收集技术做了一些修改:在JFFS2 中,所有的存储节点都不可以跨越 Flash 的块界限,这样就可以在回收空间时按照 Flash 的各个块为单位进行选择,将最应擦除的块擦除之后作为新的空闲块,这样可以提高效率与利用率。
JFFS2 使用了多个级别的待回块队列。在垃圾收集的时候先看 bad_used_list 链表中是否有节点,如果有,先回收该链表的节点,因为该链表中的节点由于闪存的物理原因很快要失效了。做完了 bad_used_list 链表的回收,然后回收 dirty_list 链表。垃圾收集操作的主要工作室将数据块里面的有效数据移动到空间块中,然后清除脏数据块,最后将数据块从 dirty_list 链表中摘除并且放入空间块链表。此外可以回收的队列还包括 erasable_list、very_dirty_list 等。
碎片收集是专门由最片收集内核线程负责处理的,一般情况下碎片收集进程处于睡眠状态,一旦 thread_should_wake() 操作发现 jffs2_sb_info 结构变量中的 nr_free_blocks 与 nr_erasing_blocks 总和小于触发碎片收集功能特定值6,且 dirty_size 大于 sector_size 时,系统将调用 thread_should_wake() 来发送 SIGHUP 信号给碎片收集进程并且被唤醒。每次碎片收集进程只回收一个空闲块,如果空闲块队列的空闲块数仍小于6,那么碎片收集进程再次被唤醒,一直到空闲数等于或大于6。
由于 JFFS2 中使用了多种节点,所以在进行垃圾收集的时候也必须对不同的节点及进行不同的操作。JFFS2 进行垃圾收集时也对内存文件系统中的不连续数据块进行整理。
3. 数据压缩
JFFS2 提供了数据压缩技术,数据存入 Flash 之前,JFFS2 会自动对其进行压缩。目前,内嵌JFFS2 的压缩算法很多,但仍以 zlib 算法为主,这种算法仅对 ASCII 和二进制数据文件进行压缩。在嵌入式文件系统中引入数据压缩技术,使其数据能够得到最大限度的压缩,可以提高资源的利用率,有利于提高性能和节省开发成本。
4. 平均磨损
Flash 有 NOR 和 NAND 两种类型,他们在使用寿命方面存在很大的差异,一般可以用擦除循环周期度量,NOR 的寿命限定每块大约可擦除10万次,而NAND 的每块擦除次数约为 100万次。为了提高 Flash 芯片的使用寿命,大多数用户希望擦除循环周期在 Flash 上均衡分布,这种处理技术称为“平均磨损”。
在JFFS1 中,碎片收集是序行的操作,它总是对文件系统队列头所指节点的块进行回收,如果该块填满了数据就将该数据后移,这样该块就成为空闲块。通过这种处理方式可以保证 Flash 中每块的擦除次数相同,从而提高了整个 Flash 芯片的使用寿命。
在 JFFS2 中进行碎片收集时,随机将干净块的内容移到空闲块,随后擦除干净块内容再写入新的数据。在 JFFS2 中,它单独处理每个擦除块,由于每次回收的是一块,碎片收集程序能够提高回收的工作效率,并且能够自动地决定接下来该回收哪一块。每个擦除块可能是多种状态中的提高回收的工作效率,并且能够自动地决定接下来该回收哪一块。每个擦除块可能是多种状态中的一种状态,它基本上是由块的内容决定,JFFS2 保留了结构列表的链接数,它用来描述单个擦除块。
在JFFS2 正常运行期间,大多数的擦除块应该再 clean_list 或 dirty_list 上,clean_list 对应的节点都是有用的节点,而 dirty_list 中至少存在一个废弃节点。
碎片收集时,它用一个非常简单的方式来决定所选择的块:如果 jiffies %100% 表示取余数非零且 dirty_list 又非空,则从 dirty_list 中取得一个块,否则,从 clean_list 中选取一块,其中 jiffies 是一个计数器。
一旦从 clean_list 中取得一个干净块,那么该块中的所有数据要被全部移到其他的空闲块,然后对该块进行擦除操作,最后将其挂接到 free_list 。这样,它保证了 Flash 的平均磨损而提高了 Flash的利用率。
更多推荐
- 9348
- 0
- 0
- 0
扫一扫分享内容
- 分享
顶部
所有评论(0)