引言
隨著科技的發(fā)展和網(wǎng)絡技術的進步,計算機存儲空間愈加緊張,存儲空間對文件系統(tǒng)的功能需求越來越大,大規(guī)模的數(shù)據(jù)增長為文件存儲、非結構化數(shù)據(jù)存儲提出了新的挑戰(zhàn)。
對于許多物聯(lián)網(wǎng)設備而言,擁有一個小型且具有彈性的文件系統(tǒng)至關重要,littlefs文件系統(tǒng)應運而生。littlefs文件系統(tǒng)在2017年由Christopher Haster開發(fā),遵循Apache 2.0協(xié)議,被應用在ARM的IoT設備Mbed操作系統(tǒng)。littlefs文件系統(tǒng)能夠讓嵌入式系統(tǒng)在ROM和RAM資源有限的情況下,還具備文件系統(tǒng)基本的掉電恢復、磨損均衡的功能。
littlefs是一種極簡的嵌入式文件系統(tǒng),適配于norflash,它所采用的文件系統(tǒng)結構與運行機制,使得文件系統(tǒng)的存儲結構更加緊湊,運行中對RAM的消耗更小。它的設計策略采用了與傳統(tǒng)“使用空間換時間”完全相反的“使用時間換空間”的策略,雖然它極大地壓縮了文件系統(tǒng)存儲空間,但是運行時也增加了RAM的消耗,不可避免地帶來了隨機讀寫時IO性能的降低。
目前,OpenAtom OpenHarmony(以下簡稱“OpenHarmony”) liteos_m內核采用了littlefs作為默認的文件系統(tǒng)。本文著重介紹了littlefs文件系統(tǒng)的存儲結構,并根據(jù)對讀寫過程的分析,解析引起littlefs文件系統(tǒng)隨機讀寫IO性能瓶頸的根本原因,然后提出一些提升littlefs隨機讀寫IO性能優(yōu)化策略。
littlefs文件系統(tǒng)結構
文件系統(tǒng)存儲結構信息基本以SuperBlock為開端,然后尋找到文件系統(tǒng)根節(jié)點,再根據(jù)根節(jié)點,逐步拓展成一個文件系統(tǒng)樹形結構體。littlefs也與此類似,以SuperBlock和根目錄為起點,構建了一個樹形存儲結構。不同的是littlefs的根("/")直接附加在SuperBlock之后,與其共享元數(shù)據(jù)對(metadata pair)。littlefs中目錄或者文件都是以該根節(jié)點為起點,構建了與其他文件系統(tǒng)類似的樹形結構。
littlefs文件系統(tǒng)樹形存儲結構如下:
圖1 littlefs文件系統(tǒng)樹形存儲結構示意圖
如圖1所示,存儲littlefs文件系統(tǒng)元數(shù)據(jù)的結構為元數(shù)據(jù)對,即兩個相互輪轉、互為表里的Block。存儲SuperBlock的元數(shù)據(jù)對固定存儲在block 0和block 1,并且文件系統(tǒng)根目錄附加在SuperBlock的尾部,與SuperBlock共享元數(shù)據(jù)對。元數(shù)據(jù)的存儲是以tag的格式存儲在元數(shù)據(jù)對內,按照元數(shù)據(jù)的類型,將Tag分為標準文件、目錄、用戶數(shù)據(jù)、元數(shù)據(jù)對尾部指針等類型。littlefs借助于這些不同類型tag信息,將littlefs文件系統(tǒng)組織成結構緊湊的樹形存儲結構體。例如tail類型的tag可以將比較大的目錄結構使用多個元數(shù)據(jù)對存儲,并且使用tail類型的tag將這些元數(shù)據(jù)對連接成一個單向的鏈表。而目錄類型的tag則直接指向該目錄的元數(shù)據(jù)對,例如"tag: dir_data"類型的tag指向目錄"/data"的元數(shù)據(jù)對,而該元數(shù)據(jù)對中又可以包含子目錄或者文件(Inline類型或者outline類型)。 ?
littlefs目錄存儲結構
littlefs目錄的引用為其父目錄元數(shù)據(jù)對(metadata pair)內的一個dir類型的Tag,而其內容則占用一個或者多個元數(shù)據(jù)對。一個目錄的元數(shù)據(jù)對內既可以包含子目錄引用的Tag,也可以包含屬于該目錄下文件的Inline類型的Tag或者指向該文件的CTZ跳表的CTZ類型Tag指針。最終littlefs通過一層層目錄或者文件的索引,組成了文件系統(tǒng)的樹形存儲結構。 ?
littlefs文件存儲方式
littlefs文件系統(tǒng)為極簡的文件系統(tǒng),使用最小的存儲開銷,同時實現(xiàn)對小文件(Bytes級別)和大文件(MB級別)的支持,對小于一個Block八分之一長度的文件,采用Inline類型的方式存儲,而大于或者等于Block八分之一長度的文件則采用Outline的方式存儲(CTZ Skip-list)。
1.2.1 inline文件存儲方式
Inline文件存儲方式,如圖2所示,即將文件內容與文件名稱一同存儲在其父目錄的元數(shù)據(jù)對(metadata pair)內,一個Tag表示其名稱,一個Tag表示其內容。
圖2 littlefs Inline文件存儲結構
?1.2.2 outline文件存儲方式
Outline文件存儲方式,如圖3所示,文件其父目錄的元數(shù)據(jù)對(metadata pair)內,一個Tag表示文件名稱,另一個Tag為CTZ類型,其指向存儲文件內容的鏈表頭。
圖3 littlefs Outline文件存儲結構
CTZ跳表(CTZ skip-list)鏈表的特別之處是:
(1)CTZ跳表的頭部指向鏈表的結尾;
(2)CTZ跳表內Block內包含一個以上的跳轉指針。
若是使用常規(guī)鏈表,存儲文件前一個數(shù)據(jù)塊包含指向后一個數(shù)據(jù)塊指針,那么在文件追加或者修改內容的時候,則需要存儲文件起始塊到目標塊的所有內容拷貝到新塊內,并且更新對后一個數(shù)據(jù)塊的指針。而若是采用反向鏈表的方式,則在文件追加或者修改內容的時候,則只需要將存儲文件目標塊到鏈表結尾的塊的所有內容拷貝到新塊內,然后更新對后一個數(shù)據(jù)塊內對前一個數(shù)據(jù)塊指向的指針,這樣對于文件追加模式可以減少修改量。另外,為了加快索引,采用了跳表的方式,Block內包含一個以上的跳轉指針,規(guī)則為:若一個數(shù)據(jù)塊在CTZ skip-list鏈表內的索引值N能被 2^X整除的數(shù),那么他就存在指向N – 2^X的指針,指針的數(shù)目為ctz(N)+1。如表1,對于block 2,包含了2個指針,分別指向block 0和block 1,其它塊也是采用相同的規(guī)則。
表1 littlefs 塊的skip-list鏈表計算樣表
littlefs文件讀寫流程
以上章節(jié)針對littlefs文件系統(tǒng)結構進行了分析,接下來開始探討littlefs內部的運行機制,以讀寫流程為例,分析littlefs隨機讀寫的IO性能瓶頸。
需要提前了解的是,littlefs的文件只擁有一個緩存,寫時作為寫緩存使用,讀時作為讀緩存使用。
littlefs文件讀過程
以下圖4是littlefs讀文件的流程圖,在讀流程的開始先檢測先前是否有對文件的寫操作,即檢測文件緩存是否作為寫緩存。若是,則強制將緩存中的數(shù)據(jù)刷新到存儲器,根據(jù)文件類型和訪問位置,或者直接從文件所在的元數(shù)據(jù)對讀取,或者從存儲文件內容的CTZ跳表內的塊內讀取,再將數(shù)據(jù)拷貝到用戶緩存沖,并從存儲器預讀取數(shù)據(jù)將文件緩沖區(qū)填滿。具體過程如下: ?
圖4 littlefs文件系統(tǒng)讀過程流程圖 ?
littlefs文件寫過程
以下圖5是littlefs寫文件的流程圖,在寫流程的開始先檢測先前是否有對文件的讀操作,即檢測文件緩存是否作為讀緩存。若是,則清除緩存中的數(shù)據(jù)。若是APPEND類型的寫操作,則直接減寫位置定位到文件末尾。若寫位置超過文件長度,說明文件結尾與寫位置間存在空洞,則使用0填充文件中的空洞。對應Inline類型文件,若推測到寫后,文件長度超過了閾值,則將文件轉成Outline類型。對于Outline類型的文件,若是修改文件的內容,則需要申請新塊,并將目標塊內訪問位置之前的所有內容都拷貝到新塊,將buffer中的用戶數(shù)據(jù)寫到緩沖區(qū)或者刷新到存儲器。
注意:寫后并沒有立刻更新Inline文件的commit,或者更新Outline文件的CTZ跳表,這些操作被延遲在文件關閉或者緩沖區(qū)再次作為讀緩存的時候強制文件刷新時更新。
圖5 littlefs文件系統(tǒng)寫過程流程圖
littlefs文件隨機讀寫IO性能瓶頸分析
littlefs文件只有一個緩沖區(qū),為讀寫復用。根據(jù)littlefs運行機制,若是對文件先讀后寫,那么僅需要直接將緩沖區(qū)的數(shù)據(jù)清空,然后申請一個新塊將目標塊內訪問位置直接的數(shù)據(jù)拷貝到新塊中,然后寫數(shù)據(jù)到新塊。若是先寫后讀,那么需要將數(shù)據(jù)刷新到存儲器,同時更新文件的CTZ跳表。在這個過程中,不僅涉及到刷新數(shù)據(jù)到存儲器,而且涉及到分配新塊替換目標塊之后的所有塊從而更新CTZ跳表,出現(xiàn)多次費時的擦除塊動作。在隨機讀寫的過程中,頻繁發(fā)生讀寫切換,也就頻繁地發(fā)生申請新塊、擦除新塊(非常費時)、數(shù)據(jù)搬移等等動作,嚴重地影響了IO性能。 ?
littlefs讀寫IO性能優(yōu)化策略
由“2.3 littlefs文件隨機讀寫IO性能瓶頸分析”章節(jié)描述可知,影響littlefs文件隨機讀寫IO性能的主要原因是文件只有一個的緩存且被讀寫復用,造成在讀寫切換的過程中頻繁地發(fā)生文件刷新,申請新塊,然后執(zhí)行費時的塊擦除,再將CTZ跳表上塊內的block內容搬移到新塊,進而更新CTZ跳表,這嚴重影響了隨機讀寫IO的性能。
所以,在RAM空間允許的情況下,可以考慮“使用空間換時間”的策略,適當?shù)卦黾游募彺娴臄?shù)量,使一個文件擁有多個緩沖區(qū),而這些緩沖區(qū)對應著一個Block的大小,在一定的條件下一次刷新一個Block,從而避免過多的數(shù)據(jù)搬移。另外,littlefs的策略是“使用時間換空間”,但是每個文件都擁有一個緩沖區(qū)明顯浪費空間。因為在一段時間內,只會有一定數(shù)量的文件被執(zhí)行讀或者寫,所以可以考慮建立一個擁有一定數(shù)量的緩存池,使緩存在文件間共享。
圖6 littlefs優(yōu)化策略
優(yōu)化的策略如圖6所示,littlefs文件緩存池為一個雙向鏈表fc_pool,緩存池隨著被打開文件的個數(shù)的增長而延長,直到用戶設置的最大限制;緩存池隨著文件的關閉而逐漸縮減。
每個緩存掛載在fc_pool緩存池雙向鏈表上,當緩存被寫或者被讀時,則將緩存移到鏈表開頭,那么緩存池鏈表末尾的緩存則為待老化的緩存,可以優(yōu)先被選擇回收。
在申請緩存時,優(yōu)先從緩沖池鏈表末尾選擇空閑緩存;若無空閑緩存,則考慮搶占讀緩存;若緩存池既沒有空閑緩存也沒有讀緩存,在緩存池長度沒有達到優(yōu)化限制的情況下,則創(chuàng)建新緩存,然后將新緩沖添加到鏈表頭;若緩存池既沒有空閑緩存也沒有讀緩存,并且緩存池長度已經(jīng)達到用戶限制,那么就需要從鏈表末尾搶占一個寫緩存,并強制占有該緩存的文件執(zhí)行刷新,進而完成搶占。
在文件被關閉或者刷新時,主動釋放緩存到緩存池,掛載在雙向鏈表的末尾。
使用上述策略對文件緩存進行優(yōu)化,可以在一定程度上減少因更新文件內容而執(zhí)行的存儲器塊擦除動作,從而增加隨機讀寫的IO性能,也間接地延長了NorFlash的壽命。
總結
通過本文的講解,相信大家對于littlefs文件系統(tǒng)有了較為全面的了解。總的來說,littlefs是一種極簡的文件系統(tǒng),實現(xiàn)了文件系統(tǒng)基本的數(shù)據(jù)緩存、掉電恢復、磨損均衡等功能,在資源相對富裕的環(huán)境中,開發(fā)者們可以對其運行機制甚至存儲結構進行“使用空間換時間”的優(yōu)化策略,提升讀寫的IO性能。
學會有效地利用文件系統(tǒng)往往能起到事半功倍的作用,希望開發(fā)者能夠將所學知識有效應用到未來的開發(fā)工作中,從而提高開發(fā)工作的效率。
審核編輯:湯梓紅
?
評論