自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

littlefs原理分析—文件讀寫(五)

系統(tǒng) OpenHarmony
本文介紹了littlefs中的文件讀寫機制,到這里littlefs大部分的操作就都已經(jīng)做了分析了。

??想了解更多關(guān)于開源的內(nèi)容,請訪問:??

??51CTO 開源基礎軟件社區(qū)??

??https://ost.51cto.com??

前言

上一篇文章介紹了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,大致過程如下:

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

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)換過程步驟如下:

  1. 為文件重分配塊,將inline數(shù)據(jù)寫入塊中。
  2. commit一個新的CTZSTRUCT類型的tag。

commit過程如下圖:

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

其中,CTZSTRUCT類型的tag中包含了新分配的文件跳表頭節(jié)點的塊指針。當讀取文件,遍歷tag時,檢測到CTZSTRUCT,就會從其中文件跳表頭節(jié)點的塊指針讀取文件數(shù)據(jù)。具體跳表中讀寫文件的過程在下小節(jié)中說明。

3、outline文件讀寫

回顧outline文件的存儲結(jié)構(gòu),其數(shù)據(jù)是用一個跳表進行存儲的:

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

outline文件的讀寫通過跳表的機制完成,commit時只需要commit帶有更新后的跳表頭的CTZSTRUCT tag。下面進行具體說明。

(1)outline文件讀操作

讀取數(shù)據(jù)的步驟如下:

  1. 調(diào)用lfs_ctz_find找到目標數(shù)據(jù)所在的塊。
  2. 調(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ù)后超過當前塊:
  1. 調(diào)用lfs_ctz_find找到寫入位置所在的塊。
  2. 調(diào)用lfs_ctz_extend在寫入位置插入新的頭節(jié)點。
  3. 最后當調(diào)用lfs_file_sync或lfs_file_close時進行commit,實際將更新后的CTZSTRUCT tag寫入元數(shù)據(jù)。

當數(shù)據(jù)寫入后超過當前塊時,會涉及到跳表的更新,下面著重對這種情況進行說明。

lfs_ctz_extend

lfs_ctz_extend函數(shù)的作用是在文件寫入的位置插入新的頭節(jié)點。其步驟如下:

  1. 分配一個新塊作為新的頭節(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。

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

  1. 將新的頭節(jié)點與左邊的后繼結(jié)點鏈接,右邊的舊的前繼節(jié)點被舍棄(但塊中內(nèi)容不會被立即擦除):

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

注:如果文件寫入位置位于文件末尾,則圖示中ctz block即為舊頭節(jié)點。調(diào)用lfs_file_seek函數(shù)可改變文件寫入位置。

commit后會寫入新的CTZSTRUCT tag,其過程如下:

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

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é)點覆蓋。

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

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ù):

// Configuration provided during initialization of the littlefs
struct lfs_config {
...
// Read a region in a block. Negative error codes are propogated
// to the user.
int (*read)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size);
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
int (*prog)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size);
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propogated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
int (*erase)(const struct lfs_config *c, lfs_block_t block);
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
int (*sync)(const struct lfs_config *c);
...
};

(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ù)是否在緩存中,分為以下幾種情況:

  1. 在pcache或rcache中:直接從cache中復制。

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

  1. 不在pcache和rcache中,且所需讀取大小小于一次能加載到cache中數(shù)據(jù)的大?。簩⒃磯K中數(shù)據(jù)加載到rcache,以便后面從rcache中讀。

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

  1. 不在pcache和rcache中,且所需讀取大小不小于一次能加載到cache中數(shù)據(jù)的大?。褐苯訌脑磯K中讀。

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

相關(guān)函數(shù):

lfs_bd_read(lfs_t *lfs,
| const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
| lfs_block_t block, lfs_off_t off,
| void *buffer, lfs_size_t size)
| // 1. 檢查是否已讀完,未讀完則繼續(xù)步驟,否則結(jié)束
|-> while (size > 0) ...
|
| // 2. 如果pcache中有緩存對應數(shù)據(jù),則從pcache中讀
|-> if (pcache && block == pcache->block &&
| off < pcache->off + pcache->size) {
| if (off >= pcache->off) {
| // is already in pcache?
| diff = lfs_min(diff, pcache->size - (off-pcache->off));
| memcpy(data, &pcache->buffer[off-pcache->off], diff);
|
| data += diff;
| off += diff;
| size -= diff;
| continue;
| }
| // pcache takes priority
| diff = lfs_min(diff, pcache->off-off);
| }
|
| // 3. 如果rcache中有緩存對應數(shù)據(jù),則從rcache中讀
|-> if (block == rcache->block &&
| off < rcache->off + rcache->size) {
| if (off >= rcache->off) {
| // is already in rcache?
| diff = lfs_min(diff, rcache->size - (off-rcache->off));
| memcpy(data, &rcache->buffer[off-rcache->off], diff);
|
| data += diff;
| off += diff;
| size -= diff;
| continue;
| }
| // rcache takes priority
| diff = lfs_min(diff, rcache->off-off);
| }
|
| // 4. 如果未命中cache且size大于等于read_size,
| // 則讀取內(nèi)容大小超過cache一次加載的大小,此時從塊中讀
|-> if (size >= hint && off % lfs->cfg->read_size == 0 &&
| size >= lfs->cfg->read_size) {
| // bypass cache?
| diff = lfs_aligndown(diff, lfs->cfg->read_size);
| lfs->cfg->read(lfs->cfg, block, off, data, diff);
|
| data += diff;
| off += diff;
| size -= diff;
| continue;
| }
|
| // 5. 如果未命中cache且size小于read_size,則將塊數(shù)據(jù)加載到rcache
|-> rcache->block = block;
| rcache->off = lfs_aligndown(off, lfs->cfg->read_size);
| rcache->size = lfs_min(
| lfs_min(
| lfs_alignup(off + hint, lfs->cfg->read_size),
| lfs->cfg->block_size)
| - rcache->off,
| lfs->cfg->cache_size);
| int err = lfs->cfg->read(lfs->cfg, rcache->block,
| rcache->off, rcache->buffer, rcache->size);

(3)block device寫操作

lfs_bd_prog的作用是將源數(shù)據(jù)寫入到目標塊中。但實際上沒有立即將數(shù)據(jù)寫入的目標塊,而是先將數(shù)據(jù)復制到pcache中,等到flush操作時才將pcache中的數(shù)據(jù)寫到塊中:

littlefs原理分析#[五]文件讀寫-開源基礎軟件社區(qū)

相關(guān)函數(shù):

lfs_bd_prog(lfs_t *lfs,
| lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate,
| lfs_block_t block, lfs_off_t off,
| const void *buffer, lfs_size_t size)
| // 1. 檢查是否已寫完,未寫完則繼續(xù)步驟,否則結(jié)束
|-> while (size > 0) ...
|
| // 2. 如果pcache已準備好,則將數(shù)據(jù)復制到pcache中
|-> if (block == pcache->block &&
| off >= pcache->off &&
| off < pcache->off + lfs->cfg->cache_size) {
| // already fits in pcache?
| lfs_size_t diff = lfs_min(size,
| lfs->cfg->cache_size - (off-pcache->off));
| memcpy(&pcache->buffer[off-pcache->off], data, diff);
|
| data += diff;
| off += diff;
| size -= diff;
|
| // 2.1 如果pcache已滿,則進行flush
|-> if (pcache->size == lfs->cfg->cache_size) {
| // eagerly flush out pcache if we fill up
| lfs_bd_flush(lfs, pcache, rcache, validate);
| continue;
| }
|
| // 3. 如果pcache未準備好,則準備pcache
|-> pcache->block = block;
| pcache->off = lfs_aligndown(off, lfs->cfg->prog_size);
| pcache->size = 0;

總結(jié)

本文介紹了littlefs中的文件讀寫機制,到這里littlefs大部分的操作就都已經(jīng)做了分析了。下一篇文章將會介紹littlefs中的磨損均衡相關(guān)策略。

??想了解更多關(guān)于開源的內(nèi)容,請訪問:??

??51CTO 開源基礎軟件社區(qū)??

??https://ost.51cto.com??。

責任編輯:jianghua 來源: 51CTO開源基礎軟件社區(qū)
相關(guān)推薦

2022-11-07 15:27:07

LittlefsFetch操作

2022-11-02 15:56:45

littlefscommit機制

2022-11-09 08:52:57

littlefs目錄操作

2022-11-22 15:21:55

littlefs磨損均衡

2022-10-27 16:07:24

littlefs存儲結(jié)構(gòu)

2009-12-18 09:27:23

Ruby讀寫excel

2013-06-09 11:31:13

Android開發(fā)XML讀寫XML解析

2009-11-02 10:53:34

VB.NET INI文

2010-03-10 15:54:33

Python讀寫文件

2021-02-26 20:55:56

JavaNIO隨機

2021-08-05 10:00:02

Python編程語言

2023-01-04 13:43:24

讀寫鎖AQS共享模式

2009-10-14 10:25:52

VB.NET讀寫文本文

2009-07-06 17:47:44

2010-07-16 09:06:57

Perl文件

2010-11-19 11:32:25

Oracle存儲過程

2013-05-27 10:58:10

propertiesJava語言

2021-04-02 06:35:49

Bash讀寫文件Linux

2024-08-12 17:36:54

2024-08-23 09:06:26

點贊
收藏

51CTO技術(shù)棧公眾號