一?問題描述
在上電啟動優化中發現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_dirents?和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