littlefs原理分析—文件讀寫(五)
??想了解更多關(guān)于開源的內(nèi)容,請訪問:??
前言
上一篇文章介紹了littlefs中的目錄操作,這一篇文章則將介紹littlefs中的文件讀寫操作。
本文會根據(jù)文件的存儲類型進行介紹,即inline文件和outline文件,其讀寫過程也有差別。另外還會介紹inline文件到outline文件的轉(zhuǎn)換,以及l(fā)ittlefs底層的讀寫API。
1、inline文件讀寫
因為inline文件數(shù)據(jù)存儲于其父目錄的元數(shù)據(jù)中,inline文件的讀寫實際上通過commit機制實現(xiàn)。讀是通過遍歷tag,寫則是通過commit一個INLINESTRUCT類型的tag。
對于inline文件的數(shù)據(jù)讀取,實際上就是從其父目錄的元數(shù)據(jù)中進行讀取,其過程已在commit機制中描述。
對于inline文件的寫入,即commit一個INLINESTRUCT類型的tag,大致過程如下:
2、inline文件轉(zhuǎn)outline文件
當文件大小超過1/8 block_size、或超過文件cache大小時,inline文件會轉(zhuǎn)為outline文件,該轉(zhuǎn)換過程在文件寫入過程中觸發(fā)。inline文件轉(zhuǎn)為outline文件之后就不會再轉(zhuǎn)回inline文件,即使對文件進行truncate操作。
轉(zhuǎn)換過程步驟如下:
- 為文件重分配塊,將inline數(shù)據(jù)寫入塊中。
- commit一個新的CTZSTRUCT類型的tag。
commit過程如下圖:
其中,CTZSTRUCT類型的tag中包含了新分配的文件跳表頭節(jié)點的塊指針。當讀取文件,遍歷tag時,檢測到CTZSTRUCT,就會從其中文件跳表頭節(jié)點的塊指針讀取文件數(shù)據(jù)。具體跳表中讀寫文件的過程在下小節(jié)中說明。
3、outline文件讀寫
回顧outline文件的存儲結(jié)構(gòu),其數(shù)據(jù)是用一個跳表進行存儲的:
outline文件的讀寫通過跳表的機制完成,commit時只需要commit帶有更新后的跳表頭的CTZSTRUCT tag。下面進行具體說明。
(1)outline文件讀操作
讀取數(shù)據(jù)的步驟如下:
- 調(diào)用lfs_ctz_find找到目標數(shù)據(jù)所在的塊。
- 調(diào)用lfs_bd_read進行讀取,該函數(shù)在后文進行分析。
其中,lfs_ctz_find函數(shù)從頭節(jié)點開始,通過塊頭處儲存的跳表節(jié)點塊指針進行遍歷、尋找目標塊位置。
跳表中塊指針按固定規(guī)律分布:對block n,如果n可以被2^x整除,那么該block就含有一個指向block n-2^x的塊指針。以block 4為例:
- 4可以被2^0整除,則block 4含有4-2^0即block 3的塊指針。
- 4可以被2^1整除,則block 4含有4-2^1即block 2的塊指針。
- 4可以被2^2整除,則block 4含有4-2^2即block 0的塊指針。
由此規(guī)律,又因為塊的大小是固定的,那么只要知道文件的偏移位置,就可以獲取該偏移位置所在block在跳表中的序號、該塊上有幾個塊指針等信息。lfs_ctz_find函數(shù)就是根據(jù)此規(guī)律進行查找:
- 獲取跳表中塊序號:根據(jù)文件偏移和塊大小計算,相關(guān)函數(shù)為lfs_ctz_index
- 獲取塊頭部塊指針數(shù)量:用ctz指令,ctz(塊序號)
(2)outline文件寫操作
outline文件寫入數(shù)據(jù)時又分為兩種情況,其寫入步驟也不同:
- 如果寫入數(shù)據(jù)后不超過當前塊,則調(diào)用lfs_bd_prog進行寫入。該步驟相對簡單。
- 如果寫入數(shù)據(jù)后超過當前塊:
- 調(diào)用lfs_ctz_find找到寫入位置所在的塊。
- 調(diào)用lfs_ctz_extend在寫入位置插入新的頭節(jié)點。
- 最后當調(diào)用lfs_file_sync或lfs_file_close時進行commit,實際將更新后的CTZSTRUCT tag寫入元數(shù)據(jù)。
當數(shù)據(jù)寫入后超過當前塊時,會涉及到跳表的更新,下面著重對這種情況進行說明。
lfs_ctz_extend
lfs_ctz_extend函數(shù)的作用是在文件寫入的位置插入新的頭節(jié)點。其步驟如下:
- 分配一個新塊作為新的頭節(jié)點,并調(diào)用lfs_bd_prog將原頭節(jié)點塊中的數(shù)據(jù)復制到新塊中。下圖中,調(diào)用lfs_bd_prog傳入的pcache參數(shù)為file->cache,lfs_bd_prog會先將數(shù)據(jù)寫入到file->cache中,等到需要進行flush操作時才將數(shù)據(jù)實際寫回block。
- 將新的頭節(jié)點與左邊的后繼結(jié)點鏈接,右邊的舊的前繼節(jié)點被舍棄(但塊中內(nèi)容不會被立即擦除):
注:如果文件寫入位置位于文件末尾,則圖示中ctz block即為舊頭節(jié)點。調(diào)用lfs_file_seek函數(shù)可改變文件寫入位置。
commit后會寫入新的CTZSTRUCT tag,其過程如下:
COW策略
outline文件寫入數(shù)據(jù)時是COW(copy-on-write)策略,lfs_ctz_extend函數(shù)插入新的頭節(jié)點時并不會將舊頭節(jié)點與后繼節(jié)點的鏈接斷掉。只有當最后將新的CTZSTRUCT tag寫入其父目錄的元數(shù)據(jù)中后,新的CTZSTRUCT tag中所包含的outline文件跳表頭節(jié)點才更新成功。
因此,如果發(fā)生掉電等異常情況導致outline文件的寫入操作未能完成時,其原有的數(shù)據(jù)也不會被丟棄。
如下圖,outline文件插入新的節(jié)點時不會去破壞原有的塊的數(shù)據(jù)。只有commit完成后,才會將新的頭節(jié)點寫入父目錄的元數(shù)據(jù)中,將原來的頭節(jié)點覆蓋。
4、block device讀寫
littlefs中block device相關(guān)的讀寫操作是其他各種上層讀寫操作的基礎,前文中提到的文件讀寫等操作均由block device相關(guān)的讀寫操作完成。block device相關(guān)讀寫操作是直接對具體的塊進行操作。文件讀寫、元數(shù)據(jù)commit過程中都是通過調(diào)用了block device相關(guān)的讀寫操作完成的。主要的相關(guān)函數(shù)為:
- lfs_bd_read:從源塊或cache中讀取數(shù)據(jù)。
- lfs_bd_prog:寫入數(shù)據(jù)到目標塊或cache。
- lfs_bd_flush:把cache中數(shù)據(jù)寫入到塊中。文件寫入后,只有當進行文件flush、sync或關(guān)閉操作時,才會調(diào)用lfs_bd_flush將數(shù)據(jù)實際寫入塊中,并將所有的更改進行commit。
以上函數(shù)利用cache或直接從塊中進行讀寫。
當直接從塊中進行讀寫時,是調(diào)用了用戶配置中提供的相關(guān)讀寫函數(shù):
(1)cache
block device讀寫函數(shù)均接受兩個cache,即rcache和pcache作為參數(shù),用作讀緩存和寫緩存。具體作用見后面分析。
littlefs中cache共有以下幾種:
- 全局rcache,lfs->rcache。用作rcache參數(shù)。
- 全局pcache,lfs->pcache。讀寫元數(shù)據(jù)時用作pcache參數(shù)。
- 文件的cache,file->cache。當對文件進行讀寫操作時用作pcache參數(shù)。
(2)block device讀操作
lfs_bd_read將源塊中數(shù)據(jù)讀到目標buffer中。讀取過程中,根據(jù)數(shù)據(jù)是否在緩存中,分為以下幾種情況:
- 在pcache或rcache中:直接從cache中復制。
- 不在pcache和rcache中,且所需讀取大小小于一次能加載到cache中數(shù)據(jù)的大?。簩⒃磯K中數(shù)據(jù)加載到rcache,以便后面從rcache中讀。
- 不在pcache和rcache中,且所需讀取大小不小于一次能加載到cache中數(shù)據(jù)的大?。褐苯訌脑磯K中讀。
相關(guān)函數(shù):
(3)block device寫操作
lfs_bd_prog的作用是將源數(shù)據(jù)寫入到目標塊中。但實際上沒有立即將數(shù)據(jù)寫入的目標塊,而是先將數(shù)據(jù)復制到pcache中,等到flush操作時才將pcache中的數(shù)據(jù)寫到塊中:
相關(guān)函數(shù):
總結(jié)
本文介紹了littlefs中的文件讀寫機制,到這里littlefs大部分的操作就都已經(jīng)做了分析了。下一篇文章將會介紹littlefs中的磨損均衡相關(guān)策略。