基于Flash存儲器的嵌入式文件系統(tǒng)設(shè)計
引 言
Flash 存儲器( Flash Memory) 是一種高可靠性、高密度的固態(tài)存儲器件。其存儲方式是完全非易失性的,掉電后可以保存數(shù)據(jù);可以在線寫入,并可按頁連續(xù)字節(jié)寫入,存取速度快,所以嵌入式系統(tǒng)通常使用Flash 存儲器作為存儲設(shè)備。 但Flash存儲器也存在著兩個主要缺陷:一是在重寫之前必須進(jìn)行擦除,因?yàn)镕lash 存儲器劃分成很多擦除塊(SectorOErase) ,對任何一位數(shù)據(jù)進(jìn)行修改必須先擦除整個塊(Sector) ;二是擦除塊的擦除次數(shù)有限,當(dāng)一個塊提前達(dá)到擦除次數(shù)上限時, 將導(dǎo)致整個Flash 存儲器無法使用。 所以,目前PC 機(jī)上很多成熟的基于磁盤的文件系統(tǒng)在Flash 存儲器上使用都存在著不足。
嵌入式系統(tǒng)應(yīng)具有的特點(diǎn): 一是高可靠性,在惡劣環(huán)境下系統(tǒng)仍能正常工作;二是低消耗,受成本限制系統(tǒng)設(shè)計必須量體裁衣,去除冗余;三是高效率,在占用較少資源情況下保證功能需求,這樣就要求算法簡單,效率高。 而日志文件系統(tǒng)(Log-St ruct ured File System) 在數(shù)據(jù)更新時無需將數(shù)據(jù)寫入原存儲區(qū)域,適應(yīng)Flash 存儲器無法進(jìn)行重寫這一特點(diǎn)。 目前,針對Flash 存儲器的缺陷而設(shè)計的Linux 下的J FFS 文件系統(tǒng),就是采用簡化的日志文件系統(tǒng)。 J FFS 文件系統(tǒng)將磨損均衡集成于清除機(jī)制之中,在帶來掉電可恢復(fù)功能的同時,大大減少了塊擦除的次數(shù),提高了文件系統(tǒng)的存取速度和效率。 但是,J FFS 文件系統(tǒng)無法單獨(dú)使用,或者使用于其它實(shí)時操作系統(tǒng)中。 對由于受成本和實(shí)時性限制而無法使用Linux 的一些嵌入式系統(tǒng),也就無法使用J FFS 文件系統(tǒng)。基于上述分析,該嵌入式文件系統(tǒng)適合在開源實(shí)時操作系統(tǒng)(如μC/OS-II) 和無操作系統(tǒng)的情況下使用。
嵌入式文件系統(tǒng)原理
在日志文件系統(tǒng)中,一個文件被修改后不是被寫入到原來的存儲空間,而是被加到所有內(nèi)容的后面,象日志一樣被更新,這就是日志文件系統(tǒng)的基本原理。由于同一個文件在文件系統(tǒng)中會留下不同的版本,所以系統(tǒng)需要設(shè)置一張表標(biāo)注文件的最新與以前的版本。在內(nèi)容不斷添加時為不將存儲空間占滿,系統(tǒng)設(shè)計了一種回收機(jī)制,回收無效內(nèi)容占用的空間。
日志文件系統(tǒng)在文件更新時不用將文件寫回原來的地址,這對Flash 存儲器這種存儲介質(zhì)最為適合。 文中所設(shè)計的嵌入式文件系統(tǒng)采用了日志文件系統(tǒng)的設(shè)計原理,以及J FFS 文件系統(tǒng)將磨損均衡集成于清除機(jī)制之中的方法。 該系統(tǒng)將一個可擦寫塊平分為多個簇,文件的讀寫以簇為單位進(jìn)行。簇的狀態(tài)有3 種:臟、干凈和空。臟表示所存內(nèi)容已被置為無效;干凈表示所存數(shù)據(jù)有效;空表示可以寫入數(shù)據(jù)。文件和目錄在該系統(tǒng)中被作為節(jié)點(diǎn),一個節(jié)點(diǎn)占用若干個簇,節(jié)點(diǎn)中的內(nèi)容連續(xù)存儲,但不能越過塊邊界存儲。該系統(tǒng)設(shè)置一個索引節(jié)點(diǎn),保存整個系統(tǒng)的信息,其中包含保存有各簇狀態(tài)的簇狀態(tài)表。
每一次文件更新后內(nèi)容都將被添加至末尾處,索引節(jié)點(diǎn)也被更新,總是占用最末尾的干凈簇。 回收臟簇時,將所要擦除塊中的干凈簇重寫到空簇中,再進(jìn)行塊擦除。 當(dāng)內(nèi)容寫至存儲體末端,則從頭部重新開始循環(huán)存儲。 所設(shè)計的文件系統(tǒng)的操作過程見圖1。
嵌入式文件系統(tǒng)設(shè)計
Flash 存儲器中的存儲結(jié)構(gòu)
Flash 存儲器中的存儲結(jié)構(gòu)見圖2。 該存儲器中每個簇的第一個字作為簇的狀態(tài)字,表示此簇是否為一個節(jié)點(diǎn)的首簇或空簇。 每個節(jié)點(diǎn)的首部存放此節(jié)點(diǎn)屬性(文件/目錄/索引節(jié)點(diǎn)) 和節(jié)點(diǎn)標(biāo)識號。
索引節(jié)點(diǎn)
索引節(jié)點(diǎn)存放該文件系統(tǒng)的大部分信息。 包括32 位的索引節(jié)點(diǎn)更新號、一張簇狀態(tài)表、下一個要被擦除塊的塊號、給下一個新建節(jié)點(diǎn)(文件或目錄) 的節(jié)點(diǎn)編號、系統(tǒng)根目錄信息表。系統(tǒng)每一次更新都會產(chǎn)生新的索引節(jié)點(diǎn),索引節(jié)點(diǎn)更新號加1。 按照Flash 存儲器的使用壽命10 年計算,需要每秒更新136 次以上,才能達(dá)到索引節(jié)點(diǎn)更新號的上限,所以認(rèn)為擁有最大更新號的索引節(jié)點(diǎn)為最新的索引節(jié)點(diǎn)。簇狀態(tài)表中對應(yīng)每一個簇有兩個Bit 位,表示各個簇的狀態(tài)(干凈01 ,臟11 ,空00) 。根目錄信息表存放根目錄下的各個目錄項(xiàng),每個目錄項(xiàng)包括:屬性(文件0x1/目錄0x0) 、文件名或目錄名、節(jié)點(diǎn)編號、此文件(或目錄) 對應(yīng)節(jié)點(diǎn)的起始簇地址、根目錄表的大小可變。
目錄節(jié)點(diǎn)
目錄節(jié)點(diǎn)存放的內(nèi)容有目錄名,目錄項(xiàng)個數(shù),及所有目錄項(xiàng)信息。 文件節(jié)點(diǎn)存放文件名,文件大小,文件屬性及文件內(nèi)容,內(nèi)存中的目錄結(jié)構(gòu)見圖3。
內(nèi)存數(shù)據(jù)結(jié)構(gòu)及基本操作
該文件系統(tǒng)載入(Mount ) 后,會在內(nèi)存中建立一個系統(tǒng)的映象。該映象包括:索引節(jié)點(diǎn)中的信息、目錄及文件信息、每個可擦寫塊中包含的節(jié)點(diǎn)信息、未存盤的節(jié)點(diǎn)信息。簇狀態(tài)表、索引節(jié)點(diǎn)更新號、新節(jié)點(diǎn)編號、下一擦除塊號等索引節(jié)點(diǎn)中的內(nèi)容,在內(nèi)存中均作為不同的變量。內(nèi)存中為每個文件和目錄都建立了映象,數(shù)據(jù)結(jié)構(gòu)見圖4 和圖5。
內(nèi)存中的文件節(jié)點(diǎn)不包含文件真正的數(shù)據(jù),而使用指針。文件被打開時,在內(nèi)存中創(chuàng)建一塊新存儲區(qū)域存放數(shù)據(jù),數(shù)據(jù)指針便指向此存儲區(qū),未被打開時,此指針指向空。 對于每個目錄有1 個目錄層數(shù),表示此目錄的深度,如根目錄的目錄層數(shù)為0 ,根目錄的下一級目錄則為1 ,依此類推。 存儲地址保存文件或目錄在Flash 中的地址。文件和目錄都被存在上一級目錄下,所屬目錄指針即指向上一級目錄在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),根目錄的所屬目錄指針即為空。對于同目錄下的不同節(jié)點(diǎn),在內(nèi)存中使用鏈表將其串聯(lián),同目錄文件指針即聯(lián)成鏈表。 鏈表的首指針保存在上一級目錄中,首目錄項(xiàng)指針即指向鏈表的首項(xiàng)。為提高塊擦寫的效率,存儲在同一個可擦寫塊中的各個節(jié)點(diǎn)在內(nèi)存中也建立一個鏈表,塊隊列指針即用于連成此鏈表。為標(biāo)識被修改的節(jié)點(diǎn),利用一個未保存隊列,未保存隊列指針即用來建立此隊列。[!--empirenews.page--]
該文件系統(tǒng)載入(mount ) 時,首先順序掃描Flash 中的每個索引節(jié)點(diǎn),查找出最大的索引節(jié)點(diǎn)更新號,此更新號對應(yīng)的索引節(jié)點(diǎn)即為最新的索引節(jié)點(diǎn)。查找到最新索引節(jié)點(diǎn)后,將簇狀態(tài)表等信息映射到內(nèi)存的數(shù)據(jù)結(jié)構(gòu)中。依據(jù)索引節(jié)點(diǎn)中的根目錄信息,遍歷所有節(jié)點(diǎn),建立內(nèi)存中的目錄文件結(jié)構(gòu),并將節(jié)點(diǎn)添加到對應(yīng)的擦寫塊隊列中。 對一個文件編輯并保存的過程見圖6。
文件打開時,先在內(nèi)存中分配一塊空間作為數(shù)據(jù)區(qū),將內(nèi)容寫入,并定位文件節(jié)點(diǎn)的數(shù)據(jù)指針指向該內(nèi)存中的數(shù)據(jù)區(qū)。如果文件內(nèi)容被修改,就將文件節(jié)點(diǎn)添加到未存盤隊列,依次寫入Flash 存儲器中,并修改簇狀態(tài)表。 保存時將內(nèi)存中數(shù)據(jù)區(qū)內(nèi)容寫入Flash 中,釋放申請的內(nèi)存空間,修改節(jié)點(diǎn)中的數(shù)據(jù)指針和簇狀態(tài)表,再將文件的所有上級目錄重新寫入Flash ,最后將更新后的索引節(jié)點(diǎn)內(nèi)容寫入Flash。如果文件未被修改,則只需修改數(shù)據(jù)指針即可。
節(jié)點(diǎn)加入未存盤隊列的順序按照目錄層數(shù)的大小排列,文件節(jié)點(diǎn)排在隊列首,目錄層數(shù)最大的排在其后,目錄層數(shù)為1 的排在隊列末尾,根目錄不加入未存盤隊列。
嵌入式文件系統(tǒng)特殊處理機(jī)制
均衡擦寫機(jī)制
為了避免任意一個可擦除塊因擦寫次數(shù)過多而過早報廢,文件系統(tǒng)對Flash擦寫時采用了均衡擦寫機(jī)制。 考慮到系統(tǒng)的精簡性,擦寫在整片F(xiàn)lash 的各塊中依次進(jìn)行,一塊擦寫完后,下一個被擦寫的塊即為后一個塊,在系統(tǒng)的索引節(jié)點(diǎn)中保存了下一個要擦除的塊號。當(dāng)文件系統(tǒng)中的剩余空間減少到設(shè)定值時,系統(tǒng)會擦除此塊,以回收臟簇占用的空間。對應(yīng)每個可擦寫塊都有一個節(jié)點(diǎn)隊列,此塊中包含的節(jié)點(diǎn)都加入其中。塊擦除的流程見圖7。
首先,將未保存于隊列中的節(jié)點(diǎn)保存,清未保存隊列。 然后將塊隊列中的所有文件節(jié)點(diǎn)轉(zhuǎn)移到空簇中,同時將文件路徑上的各級目錄加入到未存盤隊列中。對于塊隊列中的目錄節(jié)點(diǎn),則將它和其路徑上的各級目錄加入未存盤隊列中,按照未保存隊列的順序,依次將各個目錄寫入Flash 中,最后寫入最新的索引節(jié)點(diǎn)。因?yàn)槟夸浌?jié)點(diǎn)加入未存盤隊列時,按照目錄層數(shù)的大小排列,所以按照未保存隊列的順序?qū)懭霑r,可以保證當(dāng)一個目錄要被寫入 Flash 時,它的所有下級目錄已被寫入Flash 中。 所有下級目錄在Flash 中的存儲地址都已確定。當(dāng)該文件系統(tǒng)的空間將達(dá)到存儲上限時,可能會出現(xiàn)特殊情況,即廢簇回收時,空簇的空間不足,無法將所有干凈簇重寫。文件系統(tǒng)為此建立了應(yīng)急機(jī)制,先將文件節(jié)點(diǎn)內(nèi)容存在內(nèi)存中,這時新建一個臨時未保存隊列,專門保存文件節(jié)點(diǎn),在塊擦寫完成后,將剩余的文件節(jié)點(diǎn)寫入新的空簇中,其算法與圖7 所示流程大致相同。 但是,一旦在擦寫時斷電,會導(dǎo)致該塊上的所有數(shù)據(jù)丟失。
斷電錯誤處理機(jī)制
當(dāng)系統(tǒng)遭遇斷電重新啟動后,索引節(jié)點(diǎn)中的信息會與系統(tǒng)中的狀態(tài)不符,這時便需要錯誤處理機(jī)制。 錯誤一般是索引節(jié)點(diǎn)中標(biāo)注的空簇已被寫入了數(shù)據(jù),錯誤處理就是將此簇標(biāo)志為臟簇,并查找下一個空簇重新寫入。
多任務(wù)處理機(jī)制
該文件系統(tǒng)允許同時打開多個文件,在多任務(wù)操作系統(tǒng)下,為了避免沖突建立了多任務(wù)處理機(jī)制。系統(tǒng)允許打開的多個文件在內(nèi)存中同時被編輯修改,但是對 Flash 寫入操作有限制。 處理方法是設(shè)立Flash 寫入保護(hù)區(qū),在此區(qū)中只允許當(dāng)前正在執(zhí)行的任務(wù)執(zhí)行Flash 寫入操作。 實(shí)現(xiàn)Flash 寫入保護(hù)區(qū)的方法是建立一個初始值為1 的信號量,當(dāng)一個節(jié)點(diǎn)需要Flash 寫入時,首先申請信號量,完成后再釋放信號量。 Flash 寫入保護(hù)區(qū)見圖6 、圖7。在圖6 中,空操作語句是用來對多個文件的保存進(jìn)行同步。 例如,有文件1 和文件2 需要保存,先將文件1 的內(nèi)容寫入Flash 中,文件1 路徑下的目錄節(jié)點(diǎn)被添加到未保存隊列中,再將文件2 的內(nèi)容寫入Flash 中,文件2 路徑下的目錄節(jié)點(diǎn)也被添加到未保存隊列中,最后將未保存隊列中的所有節(jié)點(diǎn)都寫入Flash 中。這樣,如果同一路徑下的兩個文件同時存盤,可避免路徑下的相同目錄節(jié)點(diǎn)被寫入兩次,從而提高了效率。不足之處在于,如果很多文件同時存盤,會導(dǎo)致索引節(jié)點(diǎn)在一段時間內(nèi)都無法寫入Flash 存儲器,有斷電丟失的危險。但對于一般嵌入式系統(tǒng)來說,很少會碰到這種情況。當(dāng)進(jìn)行Flash存儲器擦寫時,在取塊隊列首節(jié)點(diǎn)至索引節(jié)點(diǎn)寫入完成這段時間內(nèi)都不允許進(jìn)行其他Flash 存儲器的寫入操作,這是為了保證數(shù)據(jù)的完整性,同時也提高了文件系統(tǒng)的穩(wěn)定性。
無目錄文件系統(tǒng)的優(yōu)化
許多嵌入式系統(tǒng)設(shè)計中雖沒有目錄管理的要求,但是對執(zhí)行效率和資源消耗的要求較高。 對于不要求有目錄管理的精簡文件系統(tǒng),在設(shè)計時也進(jìn)行了優(yōu)化。精簡文件系統(tǒng)在Flash 中的存儲格式與上述設(shè)計相同,文件系統(tǒng)中的所有文件信息都保存在索引節(jié)點(diǎn)的根目錄信息表中。精簡文件系統(tǒng)在內(nèi)存中的映象則要簡單很多,只包含索引節(jié)點(diǎn)中的信息,包括簇狀態(tài)表、下一個擦除塊、下一個新節(jié)點(diǎn)的標(biāo)號和根目錄信息,而不用為每個文件都建立內(nèi)存中的映象,節(jié)省大量的內(nèi)存空間。 文件的編輯存盤過程簡化為:打開文件、編輯、將文件寫入Flash 存儲器、將修改后的索引節(jié)點(diǎn)寫入Flash 存儲器。擦寫則只需通過查詢根目錄信息表中的各個目錄項(xiàng),將塊中的所有文件節(jié)點(diǎn)寫入空簇即可。在無目錄管理的情況下,精簡文件系統(tǒng)占用的內(nèi)存資源可以減少,操作也可便捷,提高了效率。 對于大量只需要按名存取的簡單文件管理的小型嵌入式系統(tǒng)而言,針對Flash 存儲器的簡單文件系統(tǒng)將占用資源少,執(zhí)行效率高,有很大的應(yīng)用價值。
嵌入式文件系統(tǒng)實(shí)現(xiàn)及性能分析
該文件系統(tǒng)的實(shí)現(xiàn)采用了分層方法,分為3 層4 個部分:應(yīng)用程序接口、文件系統(tǒng)核心、操作系統(tǒng)調(diào)用接口、Flash 存儲器驅(qū)動,實(shí)現(xiàn)結(jié)構(gòu)見圖8。
實(shí)現(xiàn)平臺中RTOS 為μC/OSOII 實(shí)時操作系統(tǒng),CPU 使用三星S4510B作為處理器,F(xiàn)lash 存儲器芯片為FUJ ITSU 的29LV160 TE。 針對不同的實(shí)時操作系統(tǒng)和Flash 存儲器芯片需要實(shí)現(xiàn)不同的操作系統(tǒng)接口和Flash 存儲器驅(qū)動。
針對μC/ OSOII 編寫操作系統(tǒng)調(diào)用接口,包括5個函數(shù): ①系統(tǒng)調(diào)用接口初始化FS_Sys_Interface_Init ( ) ,創(chuàng)建互斥信號量和內(nèi)存分區(qū); ② Flash 寫入關(guān)閉FS_Sys_Write_Lock ( ) ,禁止Flash 寫入操作,調(diào)用μC/OS-II 中OSMutePend ( ) ; ③ Flash寫入打開FS_Sys_Write_Unlock ( ) ,重新允許Flash 寫入操作,調(diào)用μC/OS-II 中OSMutePost() ; ④內(nèi)存空間申請F(tuán)S_Sys_Mem_Alloc( ) 和內(nèi)存空間添加FS_Sys_Mem_Add ( ) , 都調(diào)用OSMemGet ( ) 來完成; ⑤內(nèi)存空間釋放FS_Sys_Mem_Free ( ) ,調(diào)用OSMemPut ( ) 完成,將申請的內(nèi)存塊全部釋放。針對29LV160 TE 這款Flash 存儲器芯片,定義一個FlashDef 結(jié)構(gòu)體的全局變量, 用于存儲Flash 器件信息,并且編寫針對此款Flash 的塊擦寫函數(shù)FS_Device_Sector_Erase ( ) 和數(shù)據(jù)寫入函數(shù)FJ FS_Device_Write ( ) 。
完成這兩部分的實(shí)現(xiàn)后,該系統(tǒng)就可運(yùn)行調(diào)試。 測試應(yīng)用程序接口(API) 。應(yīng)該提供的各部分功能,并在突然斷電情況下,測試文件系統(tǒng)的恢復(fù)情況。無目錄管理的精簡文件系統(tǒng)的載入,可在2μs內(nèi)完成,文件寫入耗時主要為閃存的等待時間,系統(tǒng)本身只占用不到200 個字節(jié)的內(nèi)存,產(chǎn)生的代碼段大小為7 K。完整的文件系統(tǒng)載入時,需要建立內(nèi)存中映象,耗時根據(jù)文件數(shù)量的多少而不同,一般為10μs ,產(chǎn)生的代碼段大小為11 K。系統(tǒng)寫入效率較高,在無目錄管理的配置下尤其明顯。試驗(yàn)中系統(tǒng)在多次斷電的情況下,系統(tǒng)仍能恢復(fù)至上次存盤的狀態(tài),雖會導(dǎo)致個別文件未更新,但不會導(dǎo)致文件系統(tǒng)崩潰。
結(jié) 語
針對Flash 存儲器的固有缺陷設(shè)計了一種基于Flash 存儲器的嵌入式文件系統(tǒng)。 在文件讀寫時,極大減少了Flash 存儲器擦寫的次數(shù),提高了效率。 此系統(tǒng)代碼精簡,運(yùn)行時占用內(nèi)存資源少,運(yùn)行效率高,而且有斷電保護(hù),有較高的安全性。