一 问题描述

在上电启动优化中发现Linux系统下挂载JFFS2文件系统耗时较长,以128M的NOR FLASH为例,用时接近20秒。后续单板的FLASH容量为256M,时间会更长。如此长的挂载时间,会大增加系统的上电启动时间。希望能对mount功能或JFFS2文件系统做适当优化,将256M FLASH的挂载时间降到3~5秒内,优化时需要同时保证文件系统的可靠性和读写速度,要保证兼容优化前的文件系统。

root@CMM:/$ time mount -t jffs2 /dev/mtdblock1 /FLASH0

real    0m 19.83s

user    0m 0.00s

sys     0m 19.73s

二 问题分析

与磁盘文件系统不同,JFFS2文件系统不在FLASH设备上存储文件系统结构信息,所有的信息都分散在各个数据实体节点之中,在挂载文件系统的时候,需扫描整个Flash设备,从中建立起文件系统在内存中的映像,系统在运行期间,就利用这些内存中的信息进行各种文件操作。这就造成了JFFS2文件系统挂载时间过长,尤其是FLASH比较大,文件比较多的情况下。

擦除块小结(erase block summary)补丁可以提高JFFS2文件系统的挂载速度,它最基本的思想就是用空间来换时间。具体来说,就是将擦除块每个节点的原数据信息写在这个擦除块最后的固定位置,当JFFS2挂载的时候,对每个擦除块只需要读取这个小结节点。同时该补丁具有一定的稳定性和兼容性。

●     稳定性:

If the summary node is missing, maybe because of a system power-down before it could be written to flash, nothing bad happens - JFFS2 just falls back to scanning the whole block as it would have done before.

●     兼容性:

The JFFS2 image produced by sumtool is also usable with previous kernel because the summary node is JFFS2_FEATURE_RWCOMPAT_DELETE.

(当不支持擦除块小结特性的JFFS2文件系统发现了一个属性是 JFFS2_FEATURE

_RWCOMPAT_DELETE的节点时,在垃圾回收的时候,该节点可以被删除)

三 原理介绍

    在每个擦除块的末尾,有一个8 byte长的jffs2_sum_marker节点,该sum_marker节点记录summary node信息的存储位置,summary node可以由文件系统在写操作的过程中自动产生。在文件系统挂载的时候,jffs2_scan_eraseblock()会去读取每个擦除块的最后8byte,如果验证是有效的sum_marker节点,就会更据sum_marker中的offset偏移量去读取summary node,所有在挂载中需要的信息都存放在summary node节点中,因此就没有必要扫描整个擦除块。如果没有找到有效的sum_marker则按正当的启动方式去扫描整个擦除块。

3.1 存放在flash上的节点

● jffs2_raw_summary

struct jffs2_raw_summary

{

    jint16_t magic;      /* A constant magic number. */

    jint16_t nodetype;   /* = JFFS2_NODETYPE_SUMMARY */

    jint32_t totlen;      /* Total length of this node (inc data,etc) */

    jint32_t hdr_crc;    /* CRC checksum */

    jint32_t sum_num;   /* number of sum entries */

    jint32_t cln_mkr;    /* clean marker size, 0 = no cleanmarker */

    jint32_t padded;    /* sum of the size of padding nodes */

    jint32_t sum_crc;    /* summary information CRC */

    jint32_t node_crc;   /* node CRC */

    jint32_t sum[0];     /* inode summary info */

};

● jffs2_sum_inode_flash

struct jffs2_sum_inode_flash

{

    jint16_t nodetype;  /* == JFFS2_NODETYPE_INODE */

    jint32_t inode;     /* inode number */

    jint32_t version;   /* inode version */

    jint32_t offset;    /* offset on jeb */

    jint32_t totlen;    /* record length */

};

该节点包含在扫描过程中所需的dnode节点必要信息。

● jffs2_sum_dirent_flash

struct jffs2_sum_dirent_flash

{

    jint16_t nodetype;  /* == JFFS_NODETYPE_DIRENT */

    jint32_t totlen;    /* record length */

    jint32_t offset;    /* ofset on jeb */

    jint32_t pino;      /* parent inode */

    jint32_t version;   /* dirent version */

    jint32_t ino;       /* == zero for unlink */

    uint8_t nsize;      /* dirent name size */

    uint8_t type;       /* dirent type */

    uint8_t name[0];    /* dirent name */

};

该节点包含扫描过程所需的dirent节点的必要信息。

● jffs2_sum_marker

struct jffs2_sum_marker

{

    jint32_t offset; /* Offset of the summary_node in the jeb */

    jint32_t magic; /* Magic number (identifies the sum_marker node)*/

};

通过offset偏移量可以找到summary node节点的存储位置。

● 四种节点之间的关系

 

jffs2_sum_marker存放在擦除末尾固定的8byte里,通过该结构的offset读取summary node,summary node的sum字段存放sum_inode和sum_dirent节点地址信息。

3.2 存放在内存中的节点

● jffs2_sum_unknown_mem

struct jffs2_sum_unknown_mem

{

    union jffs2_sum_mem *next;  /* pointer to the next node */

    jint16_t nodetype;          /* node type */

};

是存放在内存中的summary node的公共头部。

● jffs2_sum_inode_mem

struct jffs2_sum_inode_mem

{

    union jffs2_sum_mem *next;  /* pointer to the next node */

    jint16_t nodetype;         /* == JFFS2_NODETYPE_INODE */

    jint32_t inode;            /* inode number */

    jint32_t version;          /* inode version */

    jint32_t offset;           /* offset on jeb */

    jint32_t totlen;           /* record length */

};

存放在flash上和memory中的jffs2_sum_inode节点版本号version的不同之处是,内存中节点的版本号要高些。

● jffs2_sum_dirent_mem

struct jffs2_sum_dirent_mem

{

    union jffs2_sum_mem *next; /* pointer to the next node */

    jint16_t nodetype;         /* == JFFS_NODETYPE_DIRENT */

    jint32_t totlen;           /* record length */

    jint32_t offset;           /* ofset on jeb */

    jint32_t pino;             /* parent inode */

    jint32_t version;          /* dirent version */

    jint32_t ino;              /* == zero for unlink */

    uint8_t nsize;             /* dirent name size */

    uint8_t type;              /* dirent type */

    uint8_t name[0];           /* dirent name */

};

存放在flash上和memory中的jffs2_sum_drient节点版本号version的不同之处是,内存中节点的版本号要高些。

● union jffs2_sum_mem

union jffs2_sum_mem

{

    struct jffs2_sum_unknown_mem u;

    struct jffs2_sum_inode_mem i;

    struct jffs2_sum_dirent_mem d;

};

该信息存放一个链表上,主要用来决定节点的类型。

● jffs2_summary

struct jffs2_summary

{

    uint32_t sum_size;       /* summary data size */

    uint32_t sum_num;         /* number of summary nodes */

    uint32_t sum_padded;       /* padded size */

    union jffs2_sum_mem *sum_list_head; /* summary node list head*/

    union jffs2_sum_mem *sum_list_tail;  /* summary node list tail */

    jint32_t *sum_buf;          /* buffer for writing out summary */

};

jffs2_sb_info超级块结构扩展了一个指针,指向jffs2 _summary结构。该结构存储擦除块小结(earse block summary)的必要信息。

● 五种节点之间的关系

 

3.3 挂载过程

    传统的挂载方式和EBS挂载方式不同之处在于扫描方法,如果支持EBS,挂载的时候就去读每个擦除块末尾的8bytes找到jffs2_sum_marker从而获取擦除块小结节点信息,而不是扫描整个擦除块。

 

● 擦除块存在有效的jffs2_sum_marker

    擦除块存在有效的jffs2_sum_marker节点,表示整个擦除块已经写满。jffs2_sum_marker节点有一个offset字段,代表从该擦除块开始的地址偏移的地方存放着擦除块小结的信息。EBS会分配c->sector_size - offset (size of summary info)大小的内存,把flash上该擦除块小结的信息读到内存中,通过校验节点和数据的有效性之后,会根据该擦除块小结信息,像传统方式一样构建jffs2_raw_node_refs, jffs2_full_dirent和jffs2_inode_caches,而不用扫描整个擦除块。

● 擦除块不存在有效的jffs2_sum_marker

    擦除块不存在有效的jffs2_sum_marker,表示该擦除块没有写满,应该像传统的方式一样扫描整个擦除块同时收集擦除块小结信息。下面是收集擦除块小结信息的过程:

① JFFS2_NODETYPE_INODE,为该类型的节点分配jffs2_sum_inode_mem结构,同时拷贝必要的节点信息到jffs2_sum_inode_mem,并且增加到sum_list_tail链表中,同时统计sum_size 和sum_num大小。

② JFFS2_NODETYPE_DIRENT,为该类型的节点分配jffs2_sum_dirent_mem结构,同时拷贝必要的节点信息到jffs2_sum_dirent_mem,并且增加到sum_list_tail链表中,同时统计sum_size 和sum_num大小。

③ JFFS2_NODETYPE_CLEANMARKER,不做任何处理,在summary node中有个cln_mkr统计cleankarker大小。

④ JFFS2_NODETYPE_PADDING,增加sum_padded的值。

3.4 文件操作

   在为一个新的节点分配空间时,EBS会检查c->nextblock是否有足够的空间分配给该节点以及它的summary。如果有空间就会从目前可以使用的空间(jeb->free_size - (collected summary info) - (new node's summary size))中分配。如果没有足够的空间分配,就会为该擦除块产生summary node信息,并写到预留的flash空间中。

四 方案验证

4.1 验证步骤

● 配置新的内核,支持EBS。

File systems --->

Miscellaneous filesystems  --->

 <*> Journalling Flash File System v2 (JFFS2) support

 (0)   FFS2 debugging verbosity (0 = quiet, 2 = noisy)

 [*]   JFFS2 write-buffering support

 [ ]    Verify JFFS2 write-buffer reads

 [*]   JFFS2 summary support (EXPERIMENTAL)

 [ ]   JFFS2 XATTR support (EXPERIMENTAL)

 [ ]   Advanced compression options for JFFS2

● 制作JFFS2镜像文件

mkfs.jffs2 -e 0x20000 -p 0x20000 -d rootdir -o rootdir.jffs2

-d is the directory to use for input files, -o is the output file,-e means the earseblock

size for this flash, -p option means pad the last block to the eraseblock size.

● 为JFFS2镜像文件增加小结信息

sumtool -e 0x20000 -p -i rootdir.jffs2 -o rootdir-sum.jffs2

具体的参数要根据实际情况修改。

● 写文件到FLASH

可以尝试使用nandwrite或flashcp,前者是往NAND FLASH写数据,后者是往NOR FLASH写数据。这些命令可以在mtd-utils-1.1.0中找到,下载地址:

ftp://ftp.infradead.org/pub/mtd-utils/mtd-utils-1.1.0.tar.bz2

4.2 验证结果

 

No summary

With summary

real

 0m 19.83s

0m 0.47s

user

0m 0.00s

0m 0.01s

sys

 0m 19.73s

0m 0.46s

从验证结果来看,挂载速度有显著提高。

五 参考文献

1、JFFS2 full summary support  http://www.infradead.org/pipermail/linux-mtd/2004-November/010887.html

2、Great jffs2 speedup         http://www.infradead.org/pipermail/linux-mtd/2005-September/013857.html

3、JFFS2 mount time    http://mhonarc.axis.se/jffs-dev/msg01763.html

4、Reducing JFFS2 mount time http://www.embedded-linux.co.uk/tutorial/jffs2-summary

Logo

更多推荐