jffs2文件系统挂载

  1. jffs2挂载

1.1总体流程

函数调用关系:

VfsJffs2Bindàjffs2_mountàjffs2_fill_superàjffs2_do_mount_fsàjffs2_build_filesystemàjffs2_scan_mediumàjffs2_scan_eraseblockàjffs2_scan_inode_node/jffs2_scan_dirent_nodeàjffs2_link_node_ref

总体流程:

  1. 首先先初始化了super_blockàs_node_hash即哈希表,并且设置jffs2_sb_infoàmtd指向在初始化Flash设备驱动程序时创建的mtd_info数据结构(根据partitionNum找到的mtd_info ),它物理上描述了整个Flash板块并且提供了访问Flash的底层驱动程序,并且设置jffs2_sb_info中与flash参数有关的域,如擦除块的大小、分区大小。

  1. 为 flash 分区上所有的擦除块(jffs2_sb_infoàblocks)在内存中分配一块连续的空间并初始化各种 xxxx_list 链表首部。

  1. 遍历flash分区上的所有擦除块,读取每一个擦除块上的所有数据实体(jffs2_raw_inode或jffs2_raw_dirent)建立与相应的内核描述符jffs2_raw_node_ref之间的联系(建立该内核描述符是先在内存中一次性分配多个连续的jffs2_raw_node_ref,当有需要时从中进行分配),这里说明一下jffs2_eraseblock与jffs2_raw_node_ref的关系:分配得到的多个连续的jffs2_raw_node_ref由jffs2_eraseblock的first_node和last_node分别指向第一个和最后一个jffs2_raw_node_ref,并且为每个文件建立内核描述符jffs2_inode_cache(如果已经存在则无需创建),并且建立连接关系,如果是jffs2_raw_dirent则还需要创建jffs2_full_dirent,并且按照nhash由小到大顺序插入jffs2_inode_cacheàscan_dents,最后还需要将jffs2_inode_cache加入jffs2_sb_infoàinocache_list哈希表中。

  1. 第三步扫描完每个擦除块后可以得出擦除块的不同使用情况并且返回,根据擦除块的不同使用情况将jffs2_eraseblock加入jffs2_sb_info中不同的xxxx_list中。

  1. 当所有的擦除块都遍历完成之后,通过建立的jffs2_inode_cacheàscan_dents计算各个文件的硬连接数,计算完成之后再删除scan_dents指向的jffs2_full_dirent链表(jffs2_full_dirent的建立在挂载中就是为了计算文件的硬连接数)。

  1. 最后为根目录创建inode,根目录的ino默认为1(通过jffs2_iget函数实现,如果已存在根目录则无需创建),并且将super_blockàs_root以及Vnodeàdata指向根目录对应的jffs2_inode,启动GC内核线程调用,最后将各种操作挂载到结构体mount以及Vnode上,挂载后的逻辑图如图20所示

图1 mount挂载后的数据结构图

1.2主要函数讲解

1.2.1 jffs2_mount

该函数首先先初始化了super_blockàs_node_hash即哈希表,并且通过partitionNum找到相应的mtd_info,与jffs2_sb_infoàmtd建立联系,它物理上描述了整个Flash板块并且提供了访问Flash的底层驱动程序,除此之外还设置jffs2_sb_info中与flash参数有关的域,如擦除块的大小、分区大小。

接着初始化slab以及compressors,再调用jffs2_fill_super函数,最后开启垃圾回收线程,并且建立与根目录对应的jffs2_inode的联系

图2 jffs2_mount函数

  1. jffs2_fill_super

首先根据 mtd_info 数据结构的相应域来设置 jffs2_sb_info 中与 flash 参数有关的域:擦除块大小和分区大小(mtd_info 数据结构在 flash 驱动程序初始化中已创建好)。

再调用jffs2_do_mount_fs函数,最后根据jffs2_iget来获取或建立相应的根目录,根目录的ino为1。(jffs2_iget在之后会有详细介绍)

图 SEQ 图 \*ARABIC 2 jffs2_fill_super函数

  1. jffs2_do_mount_fs

这个函数完成挂载jffs2 文件系统的绝大部分工作,详见下文分析,这里仅罗列之:

1. 创建擦除块描述符数组 jffs2_sb_info.blocks[]数组,初始化 jffs2_sb_info 的相应域

2. 扫描整个 flash 分区,为所有的数据实体建立内核描述符 jffs2_raw_node_ref、为所有的文件创建内核描述符 jffs2_inode_cache

3. 将所有文件的 jffs2_inode_cache 加入 hash 表,检查 flash 上所有数据实体的有效性

4. 根据擦除块的内容,将其描述符加入 jffs2_sb_info 中相应的 xxxx_list 链表。

在这个函数中仅为flash 分区上所有的擦除块(jffs2_sb_info.blocks)在内存中分配一块连续的空间并初始化各种 xxxx_list 链表首部,然后调用jffs2_build_filesystem函数完成挂载文件系统的绝大部分操作。

图 SEQ 图 \*ARABIC 3 jffs2_do_mount_fs函数

  1. jffs2_build_filesystem

该函数的主要步骤如下:

1.扫描整个 flash 分区,为所有的数据实体建立内核描述符 jffs2_raw_node_ref、为所有的文件创建内核描述符 jffs2_inode_cache;

2. 将所有文件的 jffs2_inode_cache 加入 inocache_list 哈希表,检查flash上所有数据结点的有效性;

3. 根据擦除块的内容,将其描述符加入 jffs2_sb_info 中相应的 xxxx_list链表

由jffs2_scan_medium函数遍历flash 分区上的所有的擦除块,读取每一个擦除块上的所有数据实体,建立内核描述符jffs2_raw_node_ref,为每个文件建立内核描述符jffs2_inode_cache,并建立相互连接关系;如果是目录文件,则为其所有目录项创建相应的jffs2_full_dirent 并组织为链表,由jffs2_inode_cache的scan_dents 域指向;并将jffs2_inode_cache 加入inocache_list哈希表;最后,根据擦除块的使用情况将其描述符jffs2_eraseblock 加入jffs2_sb_info 中的xxxx_list链表。

剩下的工作分为三个阶段完成:为每个文件计算硬链接计数、删除硬链接为 0 的文件、最后释放每个目录项 jffs2_raw_dirent 所对应的上层 jffs2_full_dirent 数据结构。

上面jffs2_scan_medium已经为flash 上所有的数据实体和文件创建了内核描述符,并且进一步为所有的目录项数据实体jffs2_raw_dirent 创建了临时的jffs2_full_dirent 数据结构(它们将在jffs2_build_filesystem 函数的最后删除,目的只是计算所有文件的硬链接计数)

图 SEQ 图 \*ARABIC 4 jffs2_build_filesystem函数1

对每一个文件都调用了jffs2_build_inode_pass1 函数,它为目录文件下的所有目录项增加其所指文件的硬链接计数。下面要再次遍历所有文件的内核描述符删除那些nlink为 0 的文件。如果它是目录,那么还要减小其下的所有子目录、文件的硬链接计数。

图 SEQ 图 \*ARABIC 5 jffs2_build_filesystem函数2

先前在jffs2_scan_medium 函数中为每个 jffs2_raw_dirent 目录项建立了临时的 jffs2_full_dirent,这里逐一删除,同时把每个目录文件的 jffs2_inode_cache.scan_dents 域设置为 NULL(其它文件的这个域本来就是NULL),以标记数据实体内核描述符 jffs2_raw_node_ref 的 next_in_ino 域组成的链表的末尾。

  1. jffs2_scan_medium

图 SEQ 图 \*ARABIC 6 jffs2_scan_medium流程图

这个函数遍历flash 分区上的所有的擦除块,执行操作:

1. 读取每一个擦除块上的所有数据实体建立相应的内核描述符 jffs2_raw_node_ref;

2. 为每个文件建立内核描述符 jffs2_inode_cache,并建立相互连接关系;

3. 为目录文件的所有目录项创建相应的jffs2_full_dirent并组织为链表,由jffs2_inode_cache的scan_dents域指向;

4. 将所有文件的 jffs2_inode_cache 加入 inocache_list 哈希表;

5. 根据擦除块的使用情况将其描述符 jffs2_eraseblock 加入 jffs2_sb_info 中的xxxx_list 链表

jffs2_scan_eraseblock 函数完成了jff2_scan_medium 函数前4 条工作。

jffs2_sb_info 的 nextblock指向当前正在写入的擦除块(即被写入新的数据结点的擦除块。注意在 jffs2 上数据结点是顺序地写入flash 的)。

问题:在该函数开头就判断flashbuf是否为空,而flashbuf又是刚刚声明得来,查阅了相关资料发现在判断之前应该是缺失了一段代码。下图的中文注释为本人所加。

图 SEQ 图 \*ARABIC 7 jffs2_scan_medium

下面说明两个特殊例子:BLK_STATE_ALLFF以及BLK_STATE_BADBLOCK。

BLK_STATE_ALLFF:该擦除块上为全1,即没有任何信息,当然也没有CLEANMARKER数据实体,则将该擦除块描述符加入erase_pending_list链表,同时增加相应的引用计数。该链表中的擦除块即将被擦除,成功擦除后要在擦除块的开始写入CLEANMARKER。当一个擦除块被擦写完毕后,CLEANMARKER 节点会被写在 NOR flash 的开头。在 JFFS v1 中,如果扫描到开头的 1K 都是 0xFF 就认为这个擦写块是干净的。但是在实际的测试中发现,如果在擦写的过程中突然掉电,擦写块上也可能会有大块连续 0xFF,但是这并不表明这个擦写块是干净的。于是我们需要 CLEANMARKER 节点来确切的标识一个干净的擦写块。

BLK_STATE_BADBLOCK:代表Flash上的一块擦除块已经损坏不能使用。那么如何判断一块擦除块已经损坏呢?Flash硬件在设计时已经设计好了,当Flash中的一块擦除块损坏则在该擦除块上设置特殊的标记,而操作系统读取识别出该特殊标记即可判断其为损坏。(软硬结合)

  1. jffs2_scan_eraseblock

图 SEQ 图 \*ARABIC 8 jffs2_scan_eraseblock流程图

该函数解析一个擦除块:

1. 为擦除块建立一组在内存空间中连续的内核描述符 jffs2_raw_node_ref(调用jffs2_prealloc_raw_node_refs函数);

2. 为jffs2_raw_inode以及jffs2_raw_dirent分配相应的内核描述符jffs2_raw_node_ref,并且为擦除块中的每个文件建立内核描述符 jffs2_inode_cache,并建立相互连接关系;

3. 为擦除块中的每个jffs2_raw_dirent创建相应的 jffs2_full_dirent 并按照根据文件名称计算得出nhash值大小,从小到大组织到 jffs2_inode_cache 的scan_dents链表中去;

4. 将擦除块中所有jffs2_inode_cache加入 inocache_list 哈希表(通过jffs2_add_fd_to_list 函数将其插入由jffs2_inode_cache.scan_dents 指向的链表)。

5. 根据扫描得到擦除块的信息,将擦除块分类,返回给jffs2_scan_medium函数以便将擦除块挂载到不同的链表中。

在jffs2_scan_eraseblock函数开始时,并不会直接从头开始读取eraseblock中的内容,而是先读取在eraseblock块最后的summary节点信息,summary节点记录了擦除块中的所有节点信息,如果summary存在则可以通过读取summary信息加速挂载,具体流程与上述四个步骤相差不大,只不过扫描的flash区域减少。这是典型的用空间换取时间。

图 SEQ 图 \*ARABIC 9 关于magic的部分代码

问题:上图是关于magic变量的判断语句,本人对magic的用途还存在疑惑

在jffs2文件系统中,magic可以用于标记该擦除块为jffs2文件系统的擦除块,除此之外magic还用于栈顶来判断栈是否溢出。

  1. jffs2_scan_inode_node

该函数为数据实体分配已经创建内核描述符jffs2_raw_node_ref,如果所属文件的内核描述符jffs2_inode_cache 不存在,则创建之,并建立二者连接关系,再将jffs2_inode_cache 记录到inocache_list哈希表中。

图 SEQ 图 \*ARABIC 10 jffs2_scan_inode_node函数

  1. jffs2_scan_dirent_node

该函数为已读出的jffs2_raw_dirent 目录项数据实体分配已创建内核描述符jffs2_raw_node_ref 和临时的jff2_full_dirent。如果为相应目录的jffs2_inode_cache 尚未创建则创建之,并建立三者之间的连接关系。

jffs2_raw_dirent 其后跟随的文件名长度为nsize,而为jffs2_full_dirent.name 分配空间时多分配一个字节,用来填充字符串结束符。然后将文件名复制到jffs2_full_dirent.name 所指空间中。

图 SEQ 图 \*ARABIC 11 jffs2_scan_dirent_node函数

  1. jffs2_link_node_ref

该函数将该 jffs2_raw_node_ref 加入到 jffs2_inode_cache.nodes 链表的首部,擦除块内所有数据实体的内核描述符通过在内存中分配时的连续,以及flash_offset中的特殊标记REF_LINK_NODE并且通过next_in_ino将多次分配的多块内核描述符jffs2_raw_node_ref连接起来,链表的首尾元素由 jffs2_eraseblock数据结构的 last_node 和 first_node 指向。最后通过各个标志修改jffs2_sb_info以及jffs2_eraseblock中的相关信息。

图 SEQ 图 \*ARABIC 12 jffs2_link_node_ref函数

  1. jffs2_prealloc_raw_node_refs

jffs2_prealloc_raw_node_refs函数先取jffs2_eraseblock的last_node指针指向的jffs2_raw_node_ref,并且跳过last_node指针指向的合法节点(不理解为什么要跳过)。接着多次分配的多组jffs2_raw_node_ref直接连接的方式的代码不够理解,通过malloc函数返回的指针指向的为该内存空间的头部,可是为什么直接判断第一个节点是否为REF_LINK_NODE?我认为不是最后一个节点吗?

图 SEQ 图 \*ARABIC 13 jffs2_prealloc_raw_node_refs函数

jffs2_alloc_refblock函数在内存中分配了一块连续的空间来存放一组jffs2_raw_node_ref,并且将分配到的最后一个jffs2_raw_node_ref的flash_offset置为REF_LINK_NODE。以至于可以在第二次分配时,属于同一个jffs2_eraseblock的2组jffs2_raw_node_ref可以通过每组最后一个jffs2_raw_node_ref的next_in_ino的指针来连接。(后一句话为本人的直观猜想不一定正确)

图 SEQ 图 \*ARABIC 14 jffs2_alloc_refblock函数

  1. jffs2_build_inode_pass1

Jffs2_build_filesystem对每一个文件都调用了jffs2_build_inode_pass1函数,它为目录文件下的所有目录项增加其所指文件的硬链接计数。

先前在jffs2_scan_medium函数中为所有的文件创建jffs2_inode_cache 时,nlink 域被设置为0,它代表指向文件索引结点的目录项的个数。jffs2_build_inode_pass1 函数的核心操作就是“child_ic->nlink++”,即增加目录项所代表文件的硬链接个数。由于子目录、子文件的目录项属于父目录文件,所以为父目录下存在的每个目录项所代表的文件增加硬链接计数。

比如,如果文件(无论是否是目录)在A 目录下,在B 目录中存在一个硬链接(即B 目录的目录文件中含有代表该文件的目录项,即在flash 上存在两个代表该文件的jffs2_raw_dirent 数据实体(一个属于A 目录文件,另一个属于B 目录文件)),那么在遍历A 目录时其nlink由0 增加为1,在遍历B 目录时就会将nlink 进一步增加为2。

图 SEQ 图 \*ARABIC 15 jffs2_build_inode_pass1函数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值