盧俊輝,劉 旗,史麗娟
(江漢大學(xué) 智能制造學(xué)院,湖北 武漢 430056)
SD 卡體積小、容量大、存取方便,適于嵌入式系統(tǒng)存儲(chǔ)海量數(shù)據(jù),便于在Windows 操作系統(tǒng)中拷貝和查看,文件存儲(chǔ)格式采用FAT32 文件系統(tǒng),其存儲(chǔ)量可達(dá)2 TB。FAT32 文件系統(tǒng)主要包括引導(dǎo)記錄區(qū)(DBR)、文件分配表(FAT)、文件目錄表(FDT)和數(shù)據(jù)區(qū)(DATA)。DBR 占用邏輯0 扇區(qū)和邏輯1 扇區(qū),保存文件系統(tǒng)的重要參數(shù),如扇區(qū)數(shù)量、簇大小、文件分配表大小、根目錄簇號(hào)等;FAT 記錄簇號(hào)和簇分配狀態(tài),是FAT32 文件系統(tǒng)鏈?zhǔn)酱鎯?chǔ)的關(guān)鍵,記錄文件數(shù)據(jù)間的鏈表存儲(chǔ)關(guān)系;FDT 存儲(chǔ)文件和子目錄信息,如文件的名稱、大小、創(chuàng)建時(shí)間以及起始簇號(hào)等;DATA 存儲(chǔ)文件數(shù)據(jù)。DBR、FAT、FDT 和DATA 之間存在鏈表存儲(chǔ)關(guān)系,簇是文件系統(tǒng)記錄數(shù)據(jù)的最小存儲(chǔ)單位,其中DBR 指向空閑簇號(hào),F(xiàn)AT 指向FDT 所占簇號(hào),F(xiàn)DT 指向DATA 所占簇號(hào)。FAT32 文件系統(tǒng)寫文件實(shí)質(zhì)是更新DBR、更新FAT、寫FDT 和寫DATA,空閑簇號(hào)是寫FDT 和DATA 的目標(biāo)簇號(hào),因此DBR 保存的空閑簇號(hào)尤為重要。
SD 卡按照FAT32 文件系統(tǒng)格式化以后,DBR、FAT 按照邏輯扇區(qū)存儲(chǔ),以下邏輯扇區(qū)簡(jiǎn)稱為扇區(qū)。為DBR 預(yù)留了0 ~2 449 扇區(qū),預(yù)留扇區(qū)足夠DBR 使用。為FAT 預(yù)留了2 450 ~32 767扇區(qū),預(yù)留扇區(qū)足夠FAT 使用。FDT 和DATA 按照邏輯簇存儲(chǔ),每個(gè)簇通常占用8 個(gè)扇區(qū),以下邏輯簇簡(jiǎn)稱為簇。向SD 卡寫文件時(shí),F(xiàn)DT 首先占用空閑簇,即FDT0 占用2 簇存儲(chǔ)文件目錄,DATA 占 用 其 后 的 簇,即DATA0 占 用3 簇、DATA1 占 用4 簇,依 次DATA127 占 用130 簇。當(dāng)FDT0 寫 完128 個(gè) 文 件 目 錄,即2 簇 空 間 被 用 完,F(xiàn)DT1 開(kāi) 始 占 用131 簇,DATA128 開(kāi) 始 占 用132簇,以此類推,F(xiàn)AT32 文件系統(tǒng)鏈?zhǔn)浇Y(jié)構(gòu)見(jiàn)表1。
表1 FAT32 文件系統(tǒng)FDT 與DATA 穿插存儲(chǔ)Tab.1 FDT and DATA interleaved storage in the FAT32 file system
通過(guò)分析FAT32 文件系統(tǒng)可知,F(xiàn)DT 和DATA 總是占用空閑簇,導(dǎo)致FDT 與DATA 互相穿插。當(dāng)SD 卡存儲(chǔ)海量文件時(shí),F(xiàn)DT 和DATA 位置混亂,嵌入式系統(tǒng)讀寫文件需要通過(guò)鏈表關(guān)系花費(fèi)大量時(shí)間查詢,鏈表式存儲(chǔ)降低了嵌入式系統(tǒng)的實(shí)時(shí)性。其次,空閑簇號(hào)保存在DBR 的第二扇區(qū)內(nèi),每寫一次FDT 或DATA,需要讀取DBR 空閑簇號(hào)作為寫FDT 或DATA 目標(biāo)簇號(hào),然后DBR 空閑簇號(hào)需要增加1 并更新DBR,F(xiàn)AT32 文件系統(tǒng)缺少空閑簇號(hào)冗余備份,一旦空閑簇號(hào)讀寫出錯(cuò),則會(huì)造成FDT 和DATA 相互覆蓋,導(dǎo)致文件系統(tǒng)出錯(cuò);再次,DBR 更新一次則DBR 存儲(chǔ)空閑簇號(hào)的扇區(qū)被擦寫一次,SD 卡扇區(qū)擦寫壽命約十余萬(wàn)次,減少DBR 扇區(qū)擦寫次數(shù)很有必要;最后,嵌入式系統(tǒng)穩(wěn)定性較差,導(dǎo)致SD 卡讀寫難免出現(xiàn)差錯(cuò),而FAT32 文件系統(tǒng)缺少檢錯(cuò)功能,不及時(shí)檢錯(cuò)并糾錯(cuò)將對(duì)數(shù)據(jù)恢復(fù)過(guò)程帶來(lái)困難。
為防止FDT 和DATA 互相穿插,楊書凱等[1]提出建立空數(shù)據(jù)文件填滿存儲(chǔ)介質(zhì),然后將后期采集的數(shù)據(jù)寫到空數(shù)據(jù)文件中,但是FDT 的一個(gè)簇只能保存128 個(gè)目錄,因此該方法只能建立128 個(gè)空文件,根本不能滿足數(shù)據(jù)采集系統(tǒng)應(yīng)用;為了避免FDT 和DATA 復(fù)雜的查詢編程,石長(zhǎng)華等[2]和吳增等[3]移植znFAT 文件系統(tǒng)到嵌入式系統(tǒng),李敏等[4]移植FATS 文件系統(tǒng)到嵌入式系統(tǒng),但是znFAT 和FATS 文件系統(tǒng)依然按照FAT32 文件系統(tǒng)進(jìn)行底層處理,沒(méi)有從根本上改變文件操作方法。對(duì)于DBR 進(jìn)行冗余備份鮮見(jiàn)文獻(xiàn)報(bào)道。對(duì)于DBR、FAT、FDT 的糾錯(cuò),陳培 德 等[5]對(duì)FAT 進(jìn) 行 恢 復(fù),陳 潮 等[6]對(duì)DBR 進(jìn) 行 恢 復(fù),蔣 笑[7]對(duì)FAT 和DBR 進(jìn) 行 恢 復(fù),遲 揚(yáng)等[8]和顧廣宇等[9]對(duì)DATA 進(jìn)行恢復(fù),但是均為后期離線恢復(fù),后期數(shù)據(jù)量大,恢復(fù)難度較大。
為了解決上述問(wèn)題,本文從順序存儲(chǔ)、冗余備份和實(shí)時(shí)糾錯(cuò)3 個(gè)方面提高FAT32 文件系統(tǒng)的實(shí)時(shí)性和可靠性。針對(duì)數(shù)據(jù)采集系統(tǒng),對(duì)FAT32 文件系統(tǒng)進(jìn)行簡(jiǎn)化,簡(jiǎn)化后文件管理系統(tǒng)只能寫文件和讀文件,另外增加如下規(guī)定:所有文件格式相同、文件容量相同,文件名采用短格式,文件系統(tǒng)無(wú)子目錄;簇是文件最小單位,一個(gè)簇存儲(chǔ)一個(gè)文件,一個(gè)簇包含8 個(gè)扇區(qū),即一個(gè)文件占用4k 字節(jié)。
為防止FDT 和DATA 之間互相穿插,在DATA 前預(yù)留足夠的空間給FDT,DBR、FAT、FDT 和DATA 按照順序存儲(chǔ),它們之間的鏈?zhǔn)浇Y(jié)構(gòu)變?yōu)轭愃扑饕Y(jié)構(gòu),提高查詢效率。
以SD 卡存儲(chǔ)50 萬(wàn)個(gè)文件為例,50 萬(wàn)個(gè)文件需要占用50 萬(wàn)個(gè)文件目錄,1 個(gè)文件目錄占用32字節(jié),每簇可以存儲(chǔ)128 個(gè)文件目錄,50 萬(wàn)個(gè)文件目錄需要占用3 907 個(gè)簇,為符合二進(jìn)制計(jì)算,F(xiàn)DF 預(yù)留4 096 個(gè)簇。FDT 占用第2 到4 097 簇,從4 098 簇開(kāi)始存儲(chǔ)DATA 文件,其文件系統(tǒng)順序存儲(chǔ)見(jiàn)表2。
表2 改進(jìn)FAT32 文件系統(tǒng)FDT 與DATA 順序存儲(chǔ)Tab.2 FDT and DATA sequentially storage in the improved FAT32 file system
FAT32 文件系統(tǒng)按照順序存儲(chǔ),對(duì)DBR 更新、FAT 更新、寫FDT 和寫DATA 過(guò)程做相應(yīng)改變。DBR 更新過(guò)程如下:讀取DBR 空閑簇號(hào),如果空閑簇號(hào)小于4 096,說(shuō)明未預(yù)留4 096 個(gè)FDT 簇,SD 卡按照改進(jìn)后的方式進(jìn)行格式化,否則DBR 空閑簇號(hào)遞增保存;FAT 更新過(guò)程如下:如果DBR 空閑簇號(hào)小于4 096,F(xiàn)AT 第一個(gè)單元填充FF FF FF F8,從第2 個(gè)單元連續(xù)填充4 095 個(gè)FF FF FF F0 標(biāo)志,填滿FAT 前32 個(gè)簇空間,然后按照FAT32 文件系統(tǒng)規(guī)則在FAT適當(dāng)位置寫入FDT 占用簇號(hào)。FDT 寫過(guò)程如下:在FDT 當(dāng)前簇內(nèi)空位寫入文件目錄,1 個(gè)FDT簇存儲(chǔ)128 個(gè)文件目錄,寫滿128 個(gè)目錄后進(jìn)入下一個(gè)FDT 預(yù)留簇;DATA 寫過(guò)程沒(méi)有變化,按照FAT32 文件系統(tǒng)規(guī)則將數(shù)據(jù)寫入FDT 所指的空閑簇。
FAT32 文件系統(tǒng)改進(jìn)為順序存儲(chǔ),從第2 簇到4 097 簇存儲(chǔ)FDT,從4 098 簇開(kāi)始存儲(chǔ)DATA,順序存儲(chǔ)仍然兼容FAT32 文件系統(tǒng)規(guī)則。
FAT32 文件系統(tǒng)最重要的是空閑簇號(hào),空閑簇號(hào)就是寫FDT 或?qū)慏ATA 的下一個(gè)簇號(hào),空閑簇號(hào)存儲(chǔ)在DBR 的4 個(gè)固定字節(jié),每次寫文件需要先讀取空閑簇號(hào),然后對(duì)空閑簇號(hào)加1 并更新DBR。因?yàn)榭臻e簇號(hào)極其重要,F(xiàn)AT32 文件系統(tǒng)對(duì)DBR 進(jìn)行了備份,DBR 備份是簡(jiǎn)單的重復(fù)備份,備份僅用于FAT32 文件系統(tǒng)后期數(shù)據(jù)恢復(fù)。簡(jiǎn)單的重復(fù)備份可能會(huì)出現(xiàn)以下3 種錯(cuò)誤:一是原始數(shù)據(jù)出現(xiàn)錯(cuò)誤;二是原始數(shù)據(jù)和備份出現(xiàn)相同的錯(cuò)誤;三是原始數(shù)據(jù)和備份出現(xiàn)不同的錯(cuò)誤,簡(jiǎn)單的重復(fù)備份無(wú)法解決上述錯(cuò)誤。因?yàn)镕AT32 文件系統(tǒng)對(duì)DBR 原始數(shù)據(jù)的備份只是簡(jiǎn)單的重復(fù)備份,原始數(shù)據(jù)和備份數(shù)據(jù)之間本身沒(méi)有規(guī)律,F(xiàn)AT32 文件系統(tǒng)無(wú)法確定原始數(shù)據(jù)錯(cuò)誤還是備份數(shù)據(jù)錯(cuò)誤。空閑簇號(hào)作為更新FAT、FDT 和DATA 的重要依據(jù),一旦DBR 空閑簇號(hào)出現(xiàn)上述錯(cuò)誤,輕則部分文件被覆蓋,重則整個(gè)文件系統(tǒng)混亂,導(dǎo)致數(shù)據(jù)無(wú)法恢復(fù)。
為了防止空閑簇號(hào)出現(xiàn)錯(cuò)誤,本文對(duì)空閑簇號(hào)進(jìn)行16 次循環(huán)冗余遞增存儲(chǔ),分別存儲(chǔ)在DBR 后16 個(gè)相鄰扇區(qū)固定位置。循環(huán)冗余存儲(chǔ)不是進(jìn)行重復(fù)備份,而是對(duì)空閑簇號(hào)進(jìn)行16 次循環(huán)遞增加1 存儲(chǔ),最大空閑簇號(hào)作為有效空閑簇號(hào),其余15 個(gè)空閑簇號(hào)作為冗余備份。每次讀取DBR 空閑簇號(hào)都是按順序讀16 個(gè)空閑簇號(hào),為防止最大空閑簇號(hào)出現(xiàn)問(wèn)題,規(guī)定最大空閑簇號(hào)必須等于次大空閑簇號(hào)加1,否則以次大空閑簇號(hào)作為有效空閑簇號(hào)。
16 次循環(huán)冗余遞增存儲(chǔ)帶來(lái)兩個(gè)好處:其一,如果最大空閑簇號(hào)存儲(chǔ)錯(cuò)誤,則以次大空閑簇號(hào)作為有效空閑簇號(hào),最多影響一個(gè)文件的存儲(chǔ);其二,F(xiàn)AT32 文件系統(tǒng)每寫一次FDT 或DATA,DBR 存儲(chǔ)空閑簇號(hào)扇區(qū)必須擦寫一次,SD 卡存儲(chǔ)單元壽命也就10 萬(wàn)次,因此文件數(shù)量不能超過(guò)10 萬(wàn)個(gè)。如果采用16 個(gè)連續(xù)扇區(qū)循環(huán)冗余遞增存儲(chǔ)空閑簇號(hào),保存50 萬(wàn)個(gè)文件,每個(gè)扇區(qū)擦寫31 250 次,有效地延長(zhǎng)了SD 卡壽命。
在寫文件的過(guò)程中,SD 卡可能因外界因素導(dǎo)致存儲(chǔ)錯(cuò)誤,單片機(jī)雖然不能處理數(shù)據(jù)本身的錯(cuò)誤,但是可以發(fā)現(xiàn)違反FAT32 文件系統(tǒng)規(guī)則的錯(cuò)誤,對(duì)DBR、FAT 和FDT 進(jìn)行檢錯(cuò)和糾錯(cuò),可以避免給后期數(shù)據(jù)存儲(chǔ)帶來(lái)無(wú)法恢復(fù)的問(wèn)題。
DBR 按照16 次循環(huán)冗余遞增存儲(chǔ)空閑簇號(hào),正常情況下最大空閑簇號(hào)作為有效空閑簇號(hào),其余15 個(gè)簇號(hào)存儲(chǔ)的空閑簇號(hào)備份。寫文件前,依次讀取DBR 內(nèi)16 個(gè)簇號(hào),選取最大空閑簇號(hào)作為有效空閑簇號(hào),如果最大空閑簇號(hào)比次大空閑簇號(hào)大于1 以上,則最大空閑簇號(hào)錯(cuò)誤,那么以次大空閑簇號(hào)作為有效空閑簇號(hào),遞增加1 后更新最大空閑簇號(hào)。
經(jīng)過(guò)順序存儲(chǔ)改進(jìn)后,F(xiàn)AT 按順序存儲(chǔ)FDT 簇號(hào),每次修改FAT 時(shí),檢查當(dāng)前扇區(qū)FAT 所指的FDT 簇號(hào)是否按順序存儲(chǔ),如果未按序存儲(chǔ),則按順序修改FDT 簇號(hào)。如果FAT 恰好進(jìn)入下一扇區(qū),則需要檢查上一個(gè)扇區(qū)FDT 簇號(hào)是否按順序存儲(chǔ)。
FDT 所存儲(chǔ)的文件目錄首字節(jié)不能為0x00,因?yàn)镕AT32 系統(tǒng)規(guī)定當(dāng)FDT 文件目錄首字節(jié)為0x00 時(shí),代表FDT 文件目錄結(jié)束。每次在寫FDT 文件目錄時(shí),檢查當(dāng)前扇區(qū)存儲(chǔ)的FDT 文件目錄首字節(jié)是否為0x00。如果檢測(cè)到FDT 文件目錄首字節(jié)為0x00,簡(jiǎn)單的處理方法是首字節(jié)填充0xE5,較好的處理方法是按照前期FDT 規(guī)律進(jìn)行填充。如果寫FDT 為下一個(gè)空扇區(qū),則檢查前一個(gè)扇區(qū)存儲(chǔ)的文件目錄首字節(jié)是否為0x00。為防止當(dāng)前FDT 文件目錄后存在無(wú)效目錄,必須確保當(dāng)前FDT 文件目錄后的首字節(jié)為0x00。
以8 G 存儲(chǔ)容量SD 卡為例,采用C8051F340 單片機(jī)采集到的數(shù)據(jù)存儲(chǔ)到SD 卡,C8051F340單片機(jī)本身具有SPI 協(xié)議,因此直接采用SPI 指令讀寫SD 卡字節(jié),SPI 時(shí)鐘頻率設(shè)定為200 kHz,SD 卡與單片機(jī)SPI 協(xié)議引腳接線見(jiàn)圖1。
SD 卡上電復(fù)位后,需要使SD 卡進(jìn)入SPI 通信模式,通過(guò)SPI 總線發(fā)送指令CMD0 使卡復(fù)位,接著發(fā)送指令CMD1 激活卡進(jìn)入內(nèi)部初始化處理,最后使卡退出空閑狀態(tài)。當(dāng)SD 卡退出空閑狀態(tài)后,就可以發(fā)送其他命令來(lái)操作卡。接著發(fā)送指令CMD58 讀取SD 卡的電壓、容量、卡的大小等信息,最后發(fā)送指令CMD16 設(shè)定扇區(qū)大小為512 字節(jié)[10]。SD 卡的初始化流程見(jiàn)圖2。?
圖2 SD 卡初始化流程Fig.2 The initialization process of the SD card
SD 卡初始化完成后需要自檢,SD 卡自檢流程見(jiàn)圖3。首先讀取DBR 起始物理扇區(qū),根據(jù)DBR 內(nèi)的信息,確定FAT 起始物理扇區(qū),再根據(jù)FDT 預(yù)留空間確定FDT 起始物理扇區(qū)。為了防止FDT 和DATA 之間穿插,需要提前給FDT 預(yù)留足夠的空間,因此空閑簇號(hào)要大于FDT 預(yù)留空間,可以通過(guò)比較DBR 內(nèi)中最大的空閑簇號(hào)和FDT 預(yù)留空間來(lái)判斷是否預(yù)留了足夠的FDT 空間,若空閑簇號(hào)大于FDT 預(yù)留空間,則說(shuō)明預(yù)留了足夠的FDT 空間,否則SD 卡需要進(jìn)行格式化。
與標(biāo)準(zhǔn)的FAT32 文件系統(tǒng)格式化不同之處在于,此處SD 卡格式化增加了DBR 冗余存儲(chǔ)預(yù)留空間、FDT 與DATA 的順序存儲(chǔ)預(yù)留空間,SD 卡格式化流程見(jiàn)圖4。首先,為實(shí)現(xiàn)DBR 的16次冗余存儲(chǔ),在DBR 后面預(yù)留16 個(gè)扇區(qū),其實(shí)質(zhì)是將DBR 所占扇區(qū)數(shù)增加到16 個(gè)扇區(qū),當(dāng)然也可以增加更多扇區(qū)保存其他參數(shù),比如采集系統(tǒng)的其他信息。其次,為了保證FDT 與DATA 的順序存儲(chǔ),在FDT 后預(yù)留4 096 個(gè)簇,預(yù)留的空間足夠FDT 使用,并將FDT 的起始位置寫入DBR 內(nèi)。再次,F(xiàn)AT 首字節(jié)填充F8 FF FF FF,其后填充4 095 個(gè)FF FF FF F0,提示文件系統(tǒng)FDT 預(yù)留的4 096 個(gè)簇不能存儲(chǔ)DATA。最后,如上所述,F(xiàn)DT 預(yù)留了4 096 個(gè)簇,SD 卡格式化理論上4 096 個(gè)簇應(yīng)完全清零,為了節(jié)省時(shí)間,只需要將FDT 的首扇區(qū)清零即可。
SD 卡寫文件包含DBR 更新、FAT 更新、FDT 更新和寫DATA,為提高SD 文件存儲(chǔ)可靠性,加入必要的檢錯(cuò)和糾錯(cuò),SD 卡寫文件流程見(jiàn)圖5。首先,讀取并比較DBR 內(nèi)16 個(gè)循環(huán)冗余遞增的空閑簇號(hào),以最大的空閑簇號(hào)作為有效空閑簇號(hào),有效空閑簇號(hào)所在扇區(qū)的下一個(gè)循環(huán)扇區(qū)保存有效空閑簇號(hào)遞增加1 并更新DBR,為了防止有效空閑簇號(hào)出現(xiàn)錯(cuò)誤,要求有效空閑簇號(hào)小于其余空閑簇號(hào)增加16。其次,根據(jù)FDT 所在簇號(hào)計(jì)算出FDT 在FAT 內(nèi)的索引位置,將FDT 簇號(hào)填入FAT 索引,并對(duì)當(dāng)前FAT 進(jìn)行更新;為防止FAT 存儲(chǔ)的FDT 出錯(cuò),每次更新FAT 時(shí)檢查FDT 索引是否按順序排列,若FDT 索引混亂則重新按序排列FDT。再次,檢測(cè)前期存儲(chǔ)的FDT 文件目錄首字節(jié)是否為0x00,若是則填充0xE5,防止FDT 目錄中斷;檢查文件目錄后面字節(jié)是否為0x00,否則后面字節(jié)清零,防止FDT 增加無(wú)效文件目錄;當(dāng)檢查前期存儲(chǔ)的FDT 檢錯(cuò)和糾錯(cuò)后,再添加最新FDT 文件目錄。最后,在有效空閑簇號(hào)所指向的簇內(nèi)寫DATA,完成文件的寫入。
圖3 SD 卡自檢流程Fig.3 The self-check process of the SD card
圖4 SD 卡格式化流程Fig.4 The format process of the SD card
圖5 SD 卡寫文件流程Fig.5 The file writing process of the SD card
分析了FAT32 文件系統(tǒng)在嵌入式系統(tǒng)應(yīng)用中存在的問(wèn)題,通過(guò)預(yù)留FDT 空間來(lái)防止FDT和DATA 互相穿插,對(duì)DBR 空閑簇號(hào)進(jìn)行循環(huán)冗余遞增存儲(chǔ),對(duì)FAT32 文件系統(tǒng)格式的檢查和糾錯(cuò),通過(guò)上述方法對(duì)FAT32 文件系統(tǒng)的改進(jìn)。以C8051F340 單片機(jī)為例,實(shí)現(xiàn)SD 卡存儲(chǔ)10萬(wàn)個(gè)文件,提高了嵌入式系統(tǒng)利用SD 卡存儲(chǔ)文件的可靠性,同時(shí)完全兼容FAT32 文件系統(tǒng)。為防止FAT、FDT 和DATA 混亂,按照上述改進(jìn)后,不允許進(jìn)行刪除操作,這個(gè)問(wèn)題不影響SD 卡應(yīng)用于嵌入式系統(tǒng)數(shù)據(jù)存儲(chǔ)。