複製鏈接
請複製以下鏈接發送給好友

jffs2

鎖定
JFFS2是JFFS的後繼者,由RJournallingFlashFileSystemVersion2edHat功能,重新改寫而成。
外文名
jffs2
功    能
管理MTD設備上的日誌型文件系統
特    性
現“損耗平衡”和“數據壓縮
不足之處
掛載時間過長很差的擴展性等

jffs2簡介

JFFS2的全名為JournallingFlashFileSystemVersion2(閃存日誌型文件系統第2版),其功能就是管理在MTD設備上實現的日誌型文件系統。與其他的存儲設備存儲方案相比,JFFS2並不準備提供讓傳統文件系統也可以使用此類設備的轉換層。它只會直接在MTD設備上實現日誌結構的文件系統。JFFS2會在安裝的時候,掃描MTD設備的日誌內容,並在RAM中重新建立文件系統結構本身。
除了提供具有斷電可靠性的日誌結構文件系統,JFFS2還會在它管理的MTD設備上實現“損耗平衡”和“數據壓縮”等特性。

jffs2詳述

節點頭部定義和兼容性
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 的使用:
圖二
jffs2 jffs2
因此,當文件系統從閃存上讀節點信息後,會生成下面的映射信息:
圖三
jffs2 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-節點的物理節點鏈表的下一個節點。
圖四
jffs2 jffs2
閃存上的節點的起始偏移都是 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 將回收的節點置為過時。
當擦寫塊上所有的節點都被置為過時,就可以擦寫這個擦寫塊,回收使用它。

jffs2不足之處

掛載時間過長
JFFS2 的掛載過程需要對閃存從頭到尾的掃描,這個過程是很慢的,我們在測試中發現,掛載一個 16M 的閃存有時需要半分鐘以上的時間。
磨損平衡的隨意性(random nature)
JFFS2 對磨損平衡是用概率的方法來解決的,這很難保證磨損平衡的確定性。在某些情況下,可能造成對擦寫塊不必要的擦寫操作;在某些情況下,又會引起對磨損平衡調整的不及時。
很差的擴展性
JFFS2 中有兩個地方的處理是 O(N) 的,這使得它的擴展性很差。
首先,掛載時間同閃存的大小,閃存上節點數目成正比。
其次,雖然 JFFS2 儘可能的減少內存的佔用,但通過上面對 JFFS2 的介紹我們可以知道實際上它對內存的佔用量是同 i 節點數和閃存上的節點數成正比的。

jffs2新特性

加入到 JFFS2 中的兩個補丁程序分別解決了上面提到的掛載時間過長和磨損平衡隨意性的問題。
磨損塊小結補丁程序
這個補丁程序最基本的思想就是用空間來換時間。具體來説,就是將每個擦寫塊每個節點的原數據信息寫在這個擦寫塊的最後,當 JFFS2 掛載的時候,對每個擦寫塊只需要讀一次來讀取這個小結節點,因此大大減少了掛載時間。使用了磨損塊小結補丁程序,一個擦寫塊的結構就像下面這樣:
圖五
jffs2 jffs2
根據我們的測試,使用磨損塊小結補丁程序,掛載一個 12M 的閃存需要 2~3 秒,掛載一個 16M 的閃存需要 3~4 秒。
改進的磨損平衡補丁程序
這個補丁程序的基本思想是,記錄每個擦寫塊的擦寫次數,當閃存上各個擦寫塊的擦寫次數的差距超過某個預定的閥值,開始進行磨損平衡的調整。調整的策略是,在垃圾回收時將擦寫次數小的擦寫塊上的數據遷移到擦寫次數大的擦寫塊上。這樣一來我們提高了磨損平衡的確定性,我們可以知道什麼時候開始磨損平衡的調整,也可以知道選取哪些擦寫塊進行磨損平衡的調整。
擦寫塊頭部補丁程序
在寫改進的磨損平衡補丁程序的過程之中,我們需要記錄每個擦寫塊的擦寫次數,這個信息需要記錄在各自的擦寫塊上。可是我們發現 JFFS2 中缺少一種靈活的對每個擦寫塊的信息進行擴展的機制。於是我們為每個擦寫塊引入了擦寫塊頭部(header),這個頭部負責紀錄每個擦寫塊的信息(比如説擦寫次數),並且它提供了靈活的擴展機制,將來如果有新的信息需要記錄,可以很容易的加入到頭部之中。 [1] 
參考資料