发布网友 发布时间:2022-04-26 14:53
共1个回答
热心网友 时间:2023-10-10 15:40
节点头部定义和兼容性
JFFS2 将文件系统的数据和原数据以节点的形式存储在闪存上,具体来说节点头部的定义如下:
图一
幻数屏蔽位:0x1985 用来标识 JFFS2 文件系统。
节点类型:JFFS2 自身定义了三种节点类型,但是考虑到文件系统可扩展性和兼容性,JFFS2从 ext2 借鉴了经验,节点类型的最高两位被用来定义节点的兼容属性,具体来说有下面几种兼容属性:
JFFS2_FEATURE_INCOMPAT:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_INCOMPAT,那么 JFFS2 必须拒绝挂载(mount)文件系统。
JFFS2_FEATURE_ROCOMPAT:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_ROCOMPAT,那么 JFFS2 必须以只读的方式挂载文件系统。
JFFS2_FEATURE_RWCOMPAT_DELETE:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_RWCOMPAT_DELETE,那么在垃圾回收的时候,这个节点可以被删除。
JFFS2_FEATURE_RWCOMPAT_COPY:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_RWCOMPAT_COPY,那么在垃圾回收的时候,这个节点要被拷贝到新的位置。
节点总长度:包括节点头和数据的长度。
节点头部 CRC 校验:包含节点头部的校验码,为文件系统的可靠性提供了支持。
节点类型
JFFS2 定义了三种节点类型:
JFFS2_NODETYPE_INODE: INODE 节点包含了i-节点的原数据(i节点号,文件的组 ID, 属主 id, 访问时间,偏移,长度等),文件数据被附在 INODE 节点之后。除此之外,每个 INODE 节点还有一个版本号,它被用来维护属于一个i-节点的所有 INODE 节点的全序关系。下面举例来说明这个全序关系在 JFFS2 的使用:
图二
因此,当文件系统从闪存上读节点信息后,会生成下面的映射信息:
图三
根据这个映射信息表,文件系统就知道到相应的 INODE 节点去读取相应的文件内容。 最后要说明的是,JFFS2 支持文件数据的压缩存储,因此在 INODE 节点中还包含了所使用的压缩算法,在读取数据的时候选择相应的压缩算法来解压缩。
JFFS2_NODETYPE_DIRENT:DIRENT 节点就是把文件名与 i 节点对应起来。在 DIRENT节点中也有一个版本号,这个版本号的作用主要是用来删除一个dentry。具体来说,当我们要从一个目录中删除一个dentry时,我们要写一个 DIRENT 节点,节点中的文件名与被删除的 dentry 中的文件名相同,i 节点号置为 0,同时设置一个更高的版本号。
JFFS2_NODETYPE_CLEANMARKER:当一个擦写块被擦写完毕后,CLEANMARKER 节点会被写在 NOR flash 的开头,或 NAND flash 的 OOB(Out-Of-Band) 区域来表明这是一个干净,可写的擦写块。在 JFFS v1 中,如果扫描到开头的 1K 都是 0xFF 就认为这个擦写块是干净的。但是在实际的测试中发现,如果在擦写的过程中突然掉电,擦写块上也可能会有大块连续 0xFF,但是这并不表明这个擦写块是干净的。于是我们需要 CLEANMARKER 节点来确切的标识一个干净的擦写块。
JFFS2节点,擦写块在内存中的表示和操作
JFFS2 维护了几个链表来管理擦写块,根据擦写块上的内容,一个擦写块会在不同的链表上。具体来说,当一个擦写块上都是合法(valid)的节点时,它会在 clean_list 上;当一个擦写块包含至少一个过时(obsolete)的节点时,它会在 dirty_list 上;当一个擦写块被擦写完毕,并被写入 CLEANMARKER 节点后,它会在 free_list 上。
通常情况下,JFFS2 顺序的在擦写块上写入不同的节点,直到一个擦写块被写满。此时 JFFS2 从 free_list 上取下一个擦写块,继续从擦写块的开头开始写入节点。当 free_list 上擦写块的数量逐渐减少到一个预先设定的阀值的时候,垃圾回收就被触发了,为文件系统清理出更多的可用擦写块。 为了减少对内存的占用,JFFS2 并没有把 i 节点所有的信息都保留在内存中,而只是把那些在请求到来时不能很快获得的信息保留在内存中。具体来说,对于在闪存上的每个 i 节点,在内存里都有一个 struct jffs2_inode_cache 与之对应,这个结构里保存了 i 节点号,指向 i 节点的连接数,以及一个指向属于这个 i 节点的物理节点链表的指针。所有的 struct jffs2_inode_cache 存储在一个哈希表中。闪存上的每个节点在内存中由一个 struct jffs2_raw_node_ref 表示,这个结构里保存了此节点的物理偏移,总长度,以及两个指向 struct jffs2_raw_node_ref 的指针。一个指针指向此节点在物理擦写块上的下一个节点,另一个指针指向属于同一个 i-节点的物理节点链表的下一个节点。
图四
在闪存上的节点的起始偏移都是 4字节对齐的,所以 struct jffs2_inode_cache 中flash_offset 的最低两位没有被用到。JFFS2 正好利用最低位作为此节点是否过时的标记。
下面举一例来说明 JFFS2 是如何使用这些数据结构的。VFS 调用 iget() 来得到一个 i 节点的信息,当这个 i 节点不在缓存中的时候,VFS 就会调用 JFFS2 的 read_inode() 回调函数来得到 i 节点信息。传给 read_inode() 的参数是 i 节点号,JFFS2 用这个 i 节点号从哈希表中查找相应的 struct jffs2_inode_cache,然后利用属于这个 i 节点的节点链表从闪存上读入节点信息,建立类似于表三的映射信息。
JFFS2 挂载过程
JFFS2 的挂载过程分为四个阶段:
1) JFFS2 扫描闪存介质,检查每个节点 CRC 校验码的合法性,同时分配了 struct jffs2_inode_cache 和 struct jffs2_raw_node_ref
2) 扫描每个 i 节点的物理节点链表,标识出过时的物理节点;对每一个合法的dentry节点,将相应的 jffs2_inode_cache 中的 nlink 加一。
3 找出 nlink 为 0 的 jffs2_inode_cache,释放相应的节点。
4 释放在扫描过程中使用的临时信息。
JFFS2 垃圾回收机制
当 free_list 上的擦写块数太少了,垃圾回收就会被触发。垃圾回收主要的任务就是回收那些已经过时的节点,但是除此之外它还要考虑磨损平衡的问题。因为如果一味的从 dirty_list上选取擦写块进行垃圾回收,那么 dirty_list 上的擦写块将先于 clean_list 上的擦写块被磨损坏。JFFS2 的处理方式是以 99% 的概率从 dirty_list,1% 的概率从 clean_list 上取一个擦写块下来。由此可以看出 JFFS2 的设计思想是偏向于性能,同时兼顾磨损平衡。对这个块上每一个没有过时的节点执行相同的操作:
1 找出这个节点所属的 i 节点号(见图五)。
2 调用 iget(),建立这个 i 节点的文件映射表。
3 找出这个节点上没有过时的数据内容,并且如果合法的数据太少,JFFS2 还会合并相邻的节点。
4 将数据读入倒缓存里,然后将它拷贝到新的擦写块上。
5 将回收的节点置为过时。
当擦写块上所有的节点都被置为过时,就可以擦写这个擦写块,回收使用它。