2022年JFFS文件系统分析报告 .pdf
JFFS2 文件系统分析报告本文在深入研究jffs2 源代码基础上,对JFFS2 文件系统的实现机制进行了分析,包括关键的数据结构及其之间的联系,文件系统的注册和挂载,以及其他主要的操作流程。1 JFFS2 层次结构在 Linux 系统中, JFFS2 文件系统处于虚拟文件系统层VFS 与存储技术设备层MTD 之间,如图 1 所示。 VFS 为内核中的各种文件系统提供一个统一的抽象层,并为上层用户提供具有统一格式的接口函数;MTD 子系统整合底层芯片驱动,为上层文件系统提供了统一访问 MTD 设备 (主要是 NOR 闪存和 NAND 闪存等设备 )的接口。 JFFS2 在内存中建立超级块信息 jffs2_sb_info 管理文件系统操作,建立索引节点信息jffs2_inode_info 管理打开的文件。VFS 层的超级块super_block和索引节点inode 分别包含JFFS2 文件系统的超级块信息jffs2_sb_info和索引节点信息jffs2_inode_info ,它们是JFFS2 和 VFS 间通信的主要接口。JFFS2 文件系统的超级块信息jffs2_sb_info 包含底层MTD 设备信息 mtd_info 指针,文件系统通过该指针访问MTD 设备,实现JFFS2 和底层 MTD 设备驱动之间的通信。应用层VFS层JFFS2文件系统层NOR 闪存, NAND 闪存驱动层super_blockinodejffs2_sb_infojffs2_inode_infomtd_infoMTD 层图 1 JFFS2 文件系统层次2 JFFS2 数据实体JFFS2 在 Flash 上只存储两种类型的数据实体,分别为jffs2_raw_inode 和 jffs2_raw_ dirent。jffs2_raw_dirent :包括文件名、 ino 号、父节点ino 号、版本号、校验码等信息,它用来形成整个文件系统的层次目录结构。jffs2_raw_inode :包括文件ino 号、版本号、访问权限、修改时间、本节点所包含的数名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 13 页 - - - - - - - - - 据文件中的起始位置及本节点所包含的数据大小等信息,它用来管理文件的所有数据。一个目录文件由多个jffs2_raw_dirent组成。而普通文件,符号链接文件,设备文件,FIFO 文件等都由一个或多个jffs2_raw_inode 数据实体组成。图2 表示了一个Flash 中数据实体的逻辑分布,物理上所有的数据实体是离散分布的,其位置由写入时Flash 空闲空间位置决定的。rdrdriririnamenamedatadata,一个目录项一个文件4字节对齐rd:jffs2_raw_direntri:jffs2_raw_inode图 2 Flash 空间上的数据实体JFFS2 文件系统在挂载时扫描整个Flash,每个 jffs2_raw_inode 数据实体都会记录其所属的文件的inode 号及其他元数据, 以及数据实体中存储的数据的长度及在文件内部的偏移。而 jffs2_raw_dirent数据实体中存有目录项对应的文件的inode 号及目录项所在的目录的inode 号等信息。 JFFS2 在扫描时根据jffs2_raw_dirent 数据实体中的信息在内存中建立文件系统的目录树信息,类似的, 根据 jffs2_raw_inode 数据实体中的信息建立起文件数据的寻址信息。 为了提高文件数据的寻址效率,JFFS2 将属于同一个文件的jffs2_raw_inode 数据实体组织为一颗红黑树, 在挂载扫描过程中检测到的每一个有效的jffs2_raw_inode 都会被添加到所属文件的红黑树。在文件数据被更新的情况下,被更新的旧数据所在的jffs2_raw_inode数 据 实 体 会 被 标 记 为 无 效 ,同 时 从 文 件 的 红 黑 树中 删 除 。 然 后 将 新的 数 据 组 织 为jffs2_raw_inode 数据实体写入Flash 并将新的数据实体加入红黑树。3 JFFS2 逻辑结构与磁盘文件系统不同,JFFS2 文件系统不在Flash 设备上存储文件系统结构信息,所有的信息都分散在各个数据实体节点之中,在系统初始化的时候,扫描整个Flash 设备,从中建立起文件系统在内存中的映像,系统在运行期间,就利用这些内存中的信息进行各种文件操作。JFFS2 为每一个 Flash 设备维护一个超级块结构jffs2_sb_info , 这个结构含有整个Flash设备的信息。 通过这个数据结构,系统维护了几个重要的链表,这几个链表构成了整个文件系统的骨架。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 13 页 - - - - - - - - - 3.1 擦除块信息链表在 jffs2_sb_info 中维护着多个擦除块链表,如表1 所示,链表中每一个节点包含一个擦除块。表1 擦除块信息链表链表链表中擦除块的性质clean_list 只包含有效数据结点very_dirty_list 所含数据结点大部分都已过时dirty_list 至少含有一个过时数据结点erasable_list 所有的数据结点都过时需要擦除。但尚未“ 调度 ” 到erase_pending_list erasable_pending_wbuf_list 同erase_pending_list,但擦除必须等待wbuf 冲刷后erasing_list 当前正在擦除erase_pending_list 当前正等待擦除erase_complete_list 擦除已完成,但尚未写入CLEANMARKER free_list 擦除完成,且已经写入CLEANMARKER bad_list 含有损坏单元bad_used_list 含有损坏单元,但含有数据擦除块由 jffs2_eraseblock 数据结构表示, 该结构包含该擦除块在Flash 设备的偏移位置、该块的空间使用情况,以及所有存在于该擦除块的数据实体的链表等信息。通过这些擦除块链表,我们可以找到存在于Flash 设备上的任何一个擦除块,及其上面的数据实体,如图3所示。jffs2_sb_infojffs2_eraseblocknextprevjffs2_eraseblocknextprevjffs2_eraseblocknextprevfirst_nodelast_nodefirst_nodelast_nodefirst_nodelast_nodejffs2_raw_node_refnext_in_inojffs2_raw_node_refnext_in_inojffs2_raw_node_refnext_in_inonextprevfree_list图 3 擦除链表的示意图3.2 incache_list链表在 jffs2_sb_info 中, 系统维护着一个inocache_list 链表,链表中每个元素jffs2_inode_cache代表一个文件,包括普通文件和目录文件,由唯一的ino 号所标识。所有属于同一个文件的名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 13 页 - - - - - - - - - 节点信息jffs2_raw_node_ref 由指针形成链表,由 jffs2_inode_cache 的 notes 域指向, 这样对文件任何一个节点操作都可以通过这个链表定位,如图4 所示。jffs2_sb_infoinocache_list jffs2_inode_cacheHash table根据文件的 ino号确定nextnotesNULLjffs2_raw_node_refnext_in_inojffs2_raw_node_refnext_in_inojffs2_raw_node_refnext_in_inonextnotesnextnotes jffs2_inode_cache jffs2_inode_cachenullnullnull图 4 inocache_list 链表3.3 jffs2_inode_info链表jffs2_inode_info是在打开文件时创建的,jffs2中通过jffs2_inode_info的 fragtree 、metedata 或者dents 来组织打开文件的所有数据实体的内核描述符。普通文件包含若干jffs2_raw_inode数 据 实 体 , 它 们 的 内 核 描 述 符jffs2_raw_node_ref组 成 的 链 表 由jffs2_inode_cache 的 nodes 指向 (前面我们已经叙述)。如果是目录文件,则在打开文件时为数据实体的内核描述符jffs2_raw_node_ref 创建相应的 jffs2_full_dirent , 并组织为链表由dents指向。jffs2_inode_infofragtreemetedatadentsinocachejffs2_raw_node_refnext_in_inonextnotes jffs2_inode_cachenullinocache_listrawnext versioninonhashtypename jffs2_full_direntrawnext versioninonhashtypename jffs2_full_direntjffs2_raw_node_refnext_in_inojffs2_raw_node_refnext_in_inoNULLflash_offsetflash_offsetflash_offset_totlen_totlen_totlenjffs2_full_dnoderawofs sizefrags名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 13 页 - - - - - - - - - 图 5 目录文件打开时的jffs2_inode_info 链表如果是普通文件, 在打开文件时创建相应的jffs2_full_dnode 和 jffs2_node_frag 数据结构, 并由后者组织在由fragtree 指向的红黑树中。对于目录文件、符号链接和设备文件只有一个jffs2_raw_inode数 据 实 体 所 以 没 有 必 要 使 用 红 黑 树 则 由metadata 直 接 指 向 它 们 的jffs2_full_dnode 。图 6 展示了一个普通文件打开时的jffs2_inode_info 链表。jffs2_inode_infofragtreemetedatadentsinocachejffs2_node_fragrb_rightrb_leftrbnoderb_rightrb_leftrbnodejffs2_node_fragrb_rightrb_leftrbnode,jffs2_full_dnoderawofs sizefragsjffs2_full_dnoderawofs sizefragsjffs2_full_dnoderawofs sizefragsjffs2_raw_node_refnext_in_inojffs2_raw_node_refnext_in_inojffs2_raw_node_refnext_in_inonextnotes jffs2_inode_cachenullinocache_list图 6 普通文件打开时的jffs2_inode_info 链表4 JFFS2 文件系统注册在配置内核时选择对jffs2 的支持,那么jffs2 的源代码编译后被静态链接入内核映象,在初始化期间init 内核线程执行init_jffs2_fs函数完成jffs2 的注册, init_jffs2_fs 函数主要完成以下工作:检测基本数据结构jffs2_unknown_node 、jffs2_raw_dirent 、jffs2_raw_inode 等的大小的合法性 ; 调 用jffs2_compressors_init()函 数 分 配 解 压 缩 所 需 空 间deflate_workspace和名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 13 页 - - - - - - - - - inflate_workspace; 调用 jffs2_create_slab_caches()为数据实体jffs2_raw_dirent 和 jffs2_raw_inode 、数据实体内核描述符jffs2_raw_node_ref 、文件的内核描述符jffs2_inode_cache 、jffs2_full_dnode和 jffs2_node_frag 等数据结构通过kmem_cache_create函数创建相应的内存高速缓存调用register_filesystem函数向系统注册JFFS2 文件系统,所有已注册文件系统的file_system_type 组成一个链表,由内核全局变量file_systems 指向。其中在 jffs2 源代码文件中定义了file_system_type 类型的变量jffs2_fs_type ,其名字为“ jffs2”,而 “ get_sb” 方法为 “ jffs2_get_sb ” ,它是具体文件系统所提供的各种方法的总入口。static s truc t file_sys tem _t ype jf fs 2_fs _t yp e = .owner =THI S_ MODULE,.nam e =jf fs 2,.get_sb =jffs 2_get_sb,.kill_sb =jffs 2_kill_sb,;5 JFFS2 文件系统挂载在挂载文件系统时内核为之创建VFS 的 super_block 数据结构,以及根目录的inode、dentry 等数据结构。挂载根文件系统时函数调用链如下:m ount_root-m ount_block_root-do_mount_root-s ys _m ount -do_mount-do_new_mount-do_kern_m oun t- vfs _kern_mount- t ype- get_sb= jf fs 2_get_sbjffs2_get_sb 在初始化VFS 超级块对象时为flash 上所有的数据实体和文件建立内核描述符。 内核描述符是数据实体和文件的“ 地图 ” ,由于在 flash 中缺少对文件数据的索引机制,所以早在挂载文件系统时就必须建立文件及其数据实体的映射关系。jffs2_get_sb 主要工作:设置文件系统方法表的指针jffs2_sb_info-s_op = &jffs2_super_operations; ,它提供了访问整个文件系统的基本方法。设置 jffs2_sb_info-mtd指向在初始化Flash 设备驱动程序时创建的mtd_info 数据结构,它物理上描述了整个Flash 板块并提供了访问Flash 的底层驱动程序。从后文可见, jffs2名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 13 页 - - - - - - - - - 方法最终通过调用flash 驱动程序中将数据实体jffs2_raw_dirent或 jffs2_raw_inode 及后继数据块写入flash(或从中读出) 。真正初始化VFS 超级块 super_block 数据结构、 为 flash 上所有数据实体建立内核描述符jffs2_raw_node_ref 、 为 所 有 文 件 创 建 内 核 描 述 符jffs2_inode_cache的 任 务 交 给jffs2_do_fill_super函数完成。jffs2_do_fill_super 函数主要工作:根据 mtd_info 数据结构的相应域来设置jffs2_sb_info 中与 flash 参数有关的域:擦除块大小和分区大小。 jffs2 驱动在成功擦除了一个擦除块后,要写入类型为CLEANMARKER的数据实体来标记擦除成功完成。分配 c-wbuf , c-inocache_list 缓冲区, 调用 jffs2_do_mount_fs() 函数完成挂载jffs2 文件系统的绝大部分工作。为根目录 “/ ”创建 VFS 的 inode 和 dentry,除根目录外任何文件的inode 和 dentry 等数据结构都是等到打开文件时才创建。文件系统超级块super_block 的 s_root 指针指向根目录的 dentry,而 dentry 的 d_inode 指向其 inode,而 inode 的 i_sb 又指向文件系统超级块super_block。 创建 inode的工作由 iget内联函数完成。 iget函数的函数调用路径为:iget- sb-s_op-read_inode=jffs2_read_inode 挂载文件系统的最后还要设置jffs2_sb_info中的几个域,比如页缓冲区中的页面大小s_blocksize, 标识文件系统的“ 魔数 ”s_magic 。另外,就是要启动GC (Garbage Collecting,垃圾回收)内核线程了。jffs2 日志文件系统的特点就是任何修改都会向flash 中写入新的数据结点,而不该动原有的数据结点。当flash 可用擦除块数量低于一定的阈值后,就得唤醒GC 内核线程回收所有“ 过时的 ” 数据结点所占的空间了。jffs2_do_mount_fs() 函数主要工作:创建擦除块描述符数组jffs2_sb_info.blocks 数组,初始化jffs2_sb_info 的相应域扫描整个 flash分区,为所有的数据实体建立内核描述jffs2_raw_node_ref 、为所有的文件创建内核描述符jffs2_inode_cache 将所有文件的 jffs2_inode_cache 加入 hash表,检查 Flash上所有数据实体的有效性(注意,只检查了数据实体jffs2_raw_dirent 或jffs2_raw_inode 自身的 crc 校验值,而把后继数据的crc校验工作延迟到了真正打开文件时)根据擦除块的内容,将其描述符加入jffs2_sb_info中相应的 xxxx_list链表。jffs2_build_filesystem()函数主要工作:上文罗列了jffs2_do_mount_fs函数完成挂载jffs2 文件系统的绝大部分工作,除了第一条外其余的工作都是调用jffs2_build_filesystem函数完成的。调用 jffs2_scan_medium() 函数遍历 flash 分区上的所有的擦除块,读取每一个擦除块上的所有数据实体, 建立相应的内核描述符jffs2_raw_node_ref ,为每个文件建立内核描述符名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页,共 13 页 - - - - - - - - - jffs2_inode_cache ,并建立相互连接关系;如果是目录文件,则为其所有目录项创建相应的 jffs2_full_dirent并组织为链表,由jffs2_inode_cache的 scan_dents 域指向;并将jffs2_inode_cache 加入 inocache_list 哈希表;最后,根据擦除块的使用情况将其描述符jffs2_eraseblock 加入 jffs2_sb_info 中的 xxxx_list 链表。调用jffs2_build_inode_pass1函数为目录文件下的所有目录项增加其所指文件的硬链接计数。遍历所有文件的内核描述符删除那些nlink 为 0 的文件。释放jffs2_full_dirent数据结构了,它们在挂载文件系统时就建立就是为了统计各文件的硬链接计数,此时其使命已经完成。先前在jffs2_scan_medium 函数中为每个jffs2_raw_dirent目录项建立了临时的jffs2_full_dirent ,这里逐一删除,同时把每个目录文件的jffs2_inode_cache.scan_dents 域设置为NULL (其它文件的这个域本来就是NULL ) ,以标记数据实体内核描述符jffs2_raw_node_ref 的 next_in_ino 域组成的链表的末尾。jffs2_scan_medium()函数主要工作:调用 jffs2_scan_eraseblock() 函数,读取每一个擦除块上的所有数据实体建立相应的内核描述符 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 链表。jffs2_scan_eraseblock()函数主要工作:该函数被 jffs2_scan_medium() 调用解析一个擦除块。读取每一个擦除块上的所有数据实体建立相应的内核描述符jffs2_raw_node_ref ;调用 jffs2_scan_inode_node() 函数为每个文件建立内核描述符jffs2_inode_cache , 并建立相互连接关系;调用 jffs2_scan_dirent_node() 为目录文件的所有目录项创建相应的jffs2_full_dirent 并组织为链表,由jffs2_inode_cache 的scan_dents域指向;将所有文件的jffs2_inode_cache 加入inocache_list哈希表;jffs2_scan_inode_node()函数主要工作:该函数为数据实体创建内核描述符jffs2_raw_node_ref ,如果所属文件的内核描述符jffs2_inode_cache 不存在,则创建之,并建立二者连接关系,再将jffs2_inode_cache 记录到 inocache_list 哈希表中。创建 jffs2_inode_cache 的工作是调用jffs2_scan_make_ino_cache() 函数实现的, 它为索引结点号为ino 的文件分配一个新的jffs2_inode_cache 数据结构并加入文件系统hash表。jffs2_scan_dirent_node()函数主要工作:名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 13 页 - - - - - - - - - 在 挂 载 文件 系 统时 为 已读 出 的jffs2_raw_dirent目 录项 数 据实 体 创建 内 核描 述 符jffs2_raw_node_ref 和临时的jff2_full_dirent 。 如果为相应目录的jffs2_inode_cache 尚未创建则创建之,并建立三者之间的连接关系。6 JFFS2 文件系统中文件打开打开文件的主要操作是为文件建立inode。 创建 inode 的工作由iget 内联函数完成。 iget函数的函数调用路径为:Iget- s b-s _op-read_inode= j ffs 2_read_inode- jf fs 2_do_read_inode- init_special_inode为文件创建inode 时, iget 函数首先计算出ino 对应的散列值,然后用得到的冲突项组成链表,链表由head指向。然后用ifind_fast 函数返回该链表中相应inode 结构的地址,如果这个 inode 已经存在,则返回其地址, 并通过 _get增加其引用计数, 并且通过 wait_on_inode函数确保inode 没有被加锁。 如果相应的inode 不存在则通过get_new_inode_fast 函数分配一个新的 inode。get_new_inode_fast 函数首先从inode_cache 高速缓存中分配一个inode 结构,将其加入内核链表inode_in_use,同时增加内核统计inode_stat_nr_inodes+ 。之后调用jffs2_read_inode ,如果打开目录文件,则为每个目录项创建jffs2_full_dirent并组织为链表,由jffs2_inode_info的dents 域 指 向 , 并 为 其 惟 一 的jffs2_raw_inode创 建 相 应 的jffs2_full_dnode ,并由jffs2_inode_info的 metedata 指向 ;如果打开普通文件,为每个数据节点 创 建 相 应 的jffs2_full_dnode 、 jffs2_node_frag , 并 组 织 为 红 黑 树 , 树 根 为jffs2_inode_info-fragtree 。 上述工作都是通过get_new_inode 函数中调用jffs2_do_read_inode函数完成的。对于特殊文件(设备文件、SOCKET 、FIFO文件),他们的inode 通过init_special_inode 函数来进一步初始化。jffs2_do_read_inode 函数分两步读出有效的数据实体:首先读出不包含后继数据jffs2_ raw_dirent 或者 jffs2_raw_inode 数据实体本身,而其中的totlen 域为整个数据实体的长度,第二次再读出后继数据。同时,根据头部信息中的nodetype 字段即可得到数据实体的类型并分配相应的数据结构:为jffs2_raw_dirent分配 jffs2_full_dirent ,为 jffs2_raw_inode 分配jffs2_tmp_node_info 和 jffs2_full_dnode 。读出完后今次能够必要的crc 校验。7 JFFS2 文件系统中文件读取JFFS2 读普通文件的操作也是通过层层函数调用实现的,具体的调用过程如下:sys _read 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 13 页 - - - - - - - - - - vfs _read- file-f_op-read=do_syn c_read- filp-f_op-aio_read= generic_file_aio_read-do_generic_file_read在 do_generic_file_read 函数中需要处理预读,并且在每一个循环中通inode.mapping -a_ops-readpage 方法依次读出文件的各个页面到页缓存的相应页框中,而这个方法即为jffs2_readpage 函数。jffs2_readpage 通过如下的函数调用完成读取操作:-m apping-a_ops-readpage=j f fs 2_readpage -jf fs 2_do_readpage_unlock-jf fs 2_do_readpage_nolock-jf fs 2_re ad_inode_range-jf fs 2_read_dnode真正的读取页面操作是通过jffs2_do_readpage_nolock 调用 jffs2_read_inode_range 函数完成的,该函数用于从Flash 上读取文件的一个页框中offset ,offset+len 区域的内容到页高速缓存中。在打开文件、读inode 时已经为所有有效的jffs2_raw_inode 数据实体创建相应的jffs2_full_dnode ,并由jffs2_node_frag 加入红黑树,这个函数仅通过红黑树访问相应的数据实体即可。 函数首先找到该页框范围内offset ,offset+len 的数据实体, 然后依次读出至页高速缓存。具体读数据实体的操作由函数jffs2_read_dnode 通过依次读出各节点信息来完成。该函数读出 fd 所指数据实体内部ofs,ofs+len 区域的数据到缓冲区buf 中。 从 Flash 上读取一个数据实体时分两步进行:第一步:读出jffs2_raw_inode 结构本身,获得csize 和 dsize 信息,读取 jffs2_raw_inode 时,首先由函数jffs2_alloc_raw_inode 函数分配一个jffs2_raw_inode 数据结构, 然后由函数jffs2_flash_read 函数填充之。 第二步: 读出紧随jffs2_raw_inode 结构其后的的数据至中间缓冲区readbuf,首先进行crc 校验,再根据具体情况读至接收缓冲区buf中:若需要读出后继所有数据并且数据未被压缩,则直接将数据读入缓冲区buf 内。若需要读出后继数据且数据被压缩,则直接将接收缓冲区当作解压缩缓冲区即可。若需要读如部分后继数据且数据未被压缩,则需分配额外缓冲区,将数据解压至其中,再从缓冲区中截取需要读出部分至缓冲区buf。若需要读出部分后继数据旦数据被压缩,则首先分配解压缓冲区,将数据解压至其中,再从缓冲区中截取需要读出部分至缓冲区buf。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 13 页 - - - - - - - - - 6 JFFS2 文件系统中文件写入与读普通文件类似,JFFS2 写普通文件的操作也是通过层层函数调用实现的,具体的操作过程主要调用了如下函数:sys _write- vfs _write- file-f_op-wri te=do_sync_write- filp-f_op-aio_write =generic_ file_aio_writegeneric_file_aio_write函数通过页缓存向文件写入。在写操作开始前要获得信号量i_sem。根据待写入的数据量一次写入可能要分成若干次操作才能完成,但是在整个写入操作期间当前进程一直持有这个信号量。在进行真正写入之前先要进行必要的准备工作。主要有:如果页框在文件内的起始大于文件大小,则本次循环将在文件中造成一个洞(hole),所以得向 Flash 写入一个 jffs2_raw_node 数据实体来描述这个洞。另外,如果 pg 页框的内容不是最新的,而写写入操作没有包括整各页框,则首先得从Flash 上读出该页框的内容。由jffs2_prepare_write 函数完成这两个工作。之后由函数jffs2_commit_write函数实现向Flash 写入操作,该函数将页框pg 中start,end区间的数据写入Flash。首先应该写入一个jffs2_raw_inode 数据实体,然后再写入数据。同时创建相应的内核描述符jffs2_raw_node_ref 以及 jffs2_full_dnode 和 jffs2_node_frag ,并刷新红黑树:要么插入新节点,要么刷新过时节点,从而使红黑树只涉及有效的数据实体。具体过程:将数据实体写入Flash 前准备好数据实体(由 jffs2_alloc_raw_inode 分配 ),因为每个数据实 体 所 携 带 的 数 据 由 长 度 限 制 , 所 以 根 据 待 写 入 数 据 量 可 能 需 要 写 入 多 个jffs2_raw_inode数据实体,并根据文件的索引节点inode 来设置它。这些数据实体的jffs2_raw_inode 中还有关于该文件相同的信息,这些相同的信息首先进行设置。在写入一个数据实体前可能要首先压缩数据,压缩操作由函数jffs2_compress 函数完成,将在 buf 中长度为 datalen的原始数据压缩至comprbuf 中, 压缩后数据的长度为cdatalen。设置 jffs2_raw_inode 数据实体中与具体数据相关的项。准备好数据实体和后继数据后,就可以将它们顺序地写入Flash 了。 jffs2_write_dnode函数将 jffs2_raw_inode 和压缩过的数据写入Flash 上 phys_ofs 处:写入操作分为两步,依次写入数据实体及后继数据。在写入前还要为新的数据实体创建相应的内核描述符jffs2_raw_node_ref 和 jffs2_full_dnode 数据结构。 如果成功写入, 则设置数据实体的内核描述符的相关标志:如果后继数据为整个页框大小,则设置PREF_PRSTlNE 标志,至少也是 REF_NORMAL ,标志。然后,将其内核描述符加入文件的jffs2_inode_cache 的nodes 域指向的链表的首部,并通过jffs2_add_physical_node_ref 函数更改相应Flash 擦名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 11 页,共 13 页 - - - - - - - - - 除块和文件系统内的相关统计信息。写入操作最后通过jffs2_flash_writev调用 Flash 驱动的mtd-wretev方法实现的。写入Flash 完成后即可释放缓存压缩数据的缓冲区并释放jffs2_inode_info.em 信号量。6 JFFS2 文件系统垃圾回收策略JFFS2 是一种日志式的文件系统,数据在存储介质上以节点的形式存在。当需要增添新内容时, 就在节点链表的末端添加新的节点存储新的内容;若要修改文件的某部分,并不修改 Flash 上原有数据实体,而只是将其内核描述符标记为“过时,并在节点链表末端添加新节点保存修改后的内容。JFFS2 如此不断地在Flash 上添加新的内容,系统运行一段时间后若空白Flash 擦除块的数量小于一定阈值,GC 被唤醒释放所有过时的数据实体,该过程称为垃圾收集。由于文件系统在进行更新时并不删除原先的节点,而是重新写入一个节点。这样可以免受系统突然掉电的危险,并且在下一次系统引导时不需要进行文件系统检查,但会造成一定的空间浪费。垃圾回收主要的任务就是回收那些已经过时的节点,除此之外还要考虑磨损平衡问题。垃圾收集进程(简称 GC 进程 )专门负责该项工作,一般情况下GC 进程处于睡眠状态,当JFFS2 的超级块内容发生改变时,系统将会检查Flash 的空闲块数是否小于设定阈值 (默认阈值为6),如果 Flash 空闲块数小于6,系统将发送一个SIGUP 信号给 GC进程, GC 进程接收到SIGUP 信号后被唤醒并开始收集“垃圾”。 挂载 JFFS2 文件系统时,在 jffs2_do_fill_super函数的最后创建并启动GC 内核线程,该线程创建