1. 程式人生 > >ESP32 學習筆記(十八)Virtual filesystem

ESP32 學習筆記(十八)Virtual filesystem

Virtual filesystem

Virtual filesystem component

概述

虛擬檔案系統(VFS)元件為可以對類檔案物件執行操作的驅動程式提供統一的介面。這可以是真實的檔案系統(FAT,SPIFFS 等),也可以是有檔案類介面的裝置驅動程式。

該元件允許 C 庫函式(如 fopenfprintf )與 FS 驅動程式一起使用。在高層次,每個 FS 驅動程式與某些路徑字首相關聯。當其中一個 C 庫函式需要開啟檔案時,VFS 元件將搜尋與該檔案路徑關聯的 FS 驅動程式,並將該呼叫轉發給該驅動程式。VFS 還將給定檔案的讀取,寫入和其他呼叫轉發到同一 FS 驅動程式。

例如,可以使用 /fat 字首註冊 FAT 檔案系統驅動程式,並呼叫 fopen(“/fat/file.txt”,“w”)。然後,VFS 元件將呼叫 FAT 驅動程式的 open 函式並將 /file.txt 引數傳遞給它(以及適當的模式標誌)。對返回的 FILE *

流的所有後續 C 庫函式呼叫也將被轉發到 FAT 驅動程式。

FS註冊

要註冊 FS 驅動程式,應用程式需要在 esp_vfs_t 結構的例項中定義,並使用 FS API 的函式指標填充它:

esp_vfs_t myfs = {
    .flags = ESP_VFS_FLAG_DEFAULT,
    .write = &myfs_write,
    .open = &myfs_open,
    .fstat = &myfs_fstat,
    .close = &myfs_close,
    .read = &myfs_read,
};

ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));

根據 FS 驅動程式宣告其 API 的方式,應使用 readwrite 等,或 read_pwrite_p 等。

情況1:宣告 API 函式時沒有額外的上下文指標(FS 驅動程式是單例):

ssize_t myfs_write(int fd, const void * data, size_t size);

// In definition of esp_vfs_t:
    .flags = ESP_VFS_FLAG_DEFAULT,
    .write = &myfs_write,
// ... other members initialized

// When registering FS, context pointer (third argument) is NULL:
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));

情況2:使用額外的上下文指標宣告 API 函式(FS 驅動程式支援多個例項):

ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size);

// In definition of esp_vfs_t:
    .flags = ESP_VFS_FLAG_CONTEXT_PTR,
    .write_p = &myfs_write,
// ... other members initialized

// When registering FS, pass the FS context pointer into the third argument
// (hypothetical myfs_mount function is used for illustrative purposes)
myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size);
ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1));

// Can register another instance:
myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2));

同步輸入/輸出多路複用

如果要通過 select() 使用同步輸入/輸出多路複用,則需要使用 start_select()end_select() 函式註冊 VFS,類似於以下示例:

// In definition of esp_vfs_t:
    .start_select = &uart_start_select,
    .end_select = &uart_end_select,
// ... other members initialized

呼叫 start_select() 以設定用於檢測屬於給定 VFS 的檔案描述符的讀/寫/錯誤條件的環境。呼叫 end_select() 來停止/取消初始化/釋放由 start_select() 設定的環境。請參閱 vfs/vfs_uart.c 中 UART 外設的參考實現,尤其是函式 esp_vfs_dev_uart_register()uart_start_select()uart_end_select()

演示使用帶有 VFS 檔案描述符的 select() 的示例是 peripherals/uart_select 示例。

如果 select() 僅用於套接字檔案描述符,那麼可以啟用 CONFIG_USE_ONLY_LWIP_SELECT 選項,這可以減少程式碼大小並提高效能。

路徑

每個註冊的 FS 都有一個與之關聯的路徑字首。該字首可以被認為是該分割槽的 “mount point”(掛載點)。

如果巢狀安裝點,則在開啟檔案時將使用具有最長匹配路徑字首的安裝點。例如,假設以下檔案系統在 VFS 中註冊:

  • FS 1 on /data
  • FS 2 on /data/static
    然後:
  • 開啟名為 /data/log.txt 的檔案時將使用 FS 1
  • 開啟名為 /data/static/index.html 的檔案時將使用 FS 2
  • 即使 FS 2 中不存在 /index.html,也不會搜尋 FS 1 /static/index.html

作為一般規則,掛載點名稱必須以路徑分隔符(/)開頭,並且必須在路徑分隔符後包含至少一個字元。但是,也支援空掛載點名稱,並且可以在應用程式需要提供“回退”檔案系統或完全覆蓋VFS功能的情況下使用。如果沒有字首匹配給定的路徑,則將使用此類檔案系統。

VFS 不以任何特殊方式處理路徑名中的點(.)。VFS 不會將 ... 視為對父目錄的引用。即在上面的例子中,使用路徑 /data/static/../log.txt 不會導致呼叫 FS 1 來開啟 /log.txt。特定 FS 驅動程式(如 FATFS)可能以不同方式處理檔名中的點。

開啟檔案時,FS 驅動程式只會獲得檔案的相對路徑。例如:

  • myfs 驅動程式使用 /data 註冊為路徑字首
  • 和應用程式呼叫 fopen(“/data/config.json”,...)
  • 然後 VFS 元件將呼叫 myfs_open(“/config.json”,...)
  • myfs 驅動程式將開啟 /config.json 檔案

VFS 不會對總檔案路徑長度施加限制,但會將 FS 路徑字首限制為 ESP_VFS_PATH_MAX 字元。各個 FS 驅動程式可能有自己的檔名長度限制。

檔案描述符

檔案描述符是從 0FD_SETSIZE -1 其中 FD_SETSIZE 在newlib的 sys/types.h 中定義。最大的檔案描述符(由 CONFIG_LWIP_MAX_SOCKETS 配置)保留給套接字。VFS 元件包含一個名為 s_fd_table 的查詢表,用於將全域性檔案描述符對映到 s_vfs 陣列中註冊的 VFS 驅動程式索引。

標準 IO 流(stdin,stdout,stderr)

如果“UART for console output”menuconfig選項未設定為“None”,則 stdinstdoutstderr 配置為讀取和寫入 UART。可以將 UART0 或 UART1 用於標準 IO。預設情況下,使用 UART0,波特率為 115200,TX 引腳為 GPIO1,RX 引腳為 GPIO3。可以在 menuconfig 中更改這些引數。

寫入 stdoutstderr 會將字元傳送到 UART 傳送 FIFO。從 stdin 讀取將從 UART 接收 FIFO 中檢索字元。

預設情況下,VFS 使用簡單的函式來讀取和寫入 UART。寫入忙 - 等待直到所有資料都被放入 UART FIFO,並且讀取是非阻塞的,只返回 FIFO 中的資料。由於這種非阻塞讀取行為,更高級別的 C 庫呼叫,例如 fscanf(“%d \ n”,&var); 可能沒有預期的結果。

使用 UART 驅動程式的應用程式可能會指示 VFS 使用驅動程式的中斷驅動,阻塞讀寫功能。這可以通過呼叫 esp_vfs_dev_uart_use_driver 函式來完成。也可以使用 esp_vfs_dev_uart_use_nonblocking 呼叫恢復到基本的非阻塞函式。

VFS 還為輸入和輸出提供可選的換行轉換功能。在內部,大多數應用程式傳送和接收由 LF(‘n’’)字元終止的行。不同的終端程式可能需要不同的線路終端,例如 CR 或 CRLF。應用程式可以通過 menuconfig 或通過呼叫 esp_vfs_dev_uart_set_rx_line_endingsesp_vfs_dev_uart_set_tx_line_endings 函式單獨為輸入和輸出配置它。

標準流和 FreeRTOS 任務

stdinstdoutstderrFILE 物件在所有 FreeRTOS 任務之間共享,但指向這些物件的指標儲存在每個任務的 struct _reent 中。以下程式碼:

fprintf(stderr,“42\n”);

實際上被翻譯成這個(由前處理器):

fprintf(__ getreent() -> _stderr,“42\n”);

其中 __getreent()函式返回一個指向 struct _reent的每個任務指標 (newlib/include/sys/reent.h#L370-L417)。此結構在每個任務的 TCB 上分配。初始化任務時,struct _reent_stdin_stdout_stderr 成員被設定為 _GLOBAL_REENT_stdin_stdout_stderr 的值(即在 FreeRTOS 啟動之前使用的結構)。

這樣的設計會產生以下後果:

  • 可以為任何給定任務設定 stdinstdoutstderr,而不會影響其他任務,例如通過做 stdin = fopen(“/dev/uart/1”,“r”)
  • 使用 fclose 關閉預設 stdinstdoutstderr 將關閉 FILE 流物件 - 這將影響所有其他任務。
  • 要更改新任務的預設 stdinstdoutstderr 流,請在建立任務之前修改 _GLOBAL_REENT - > _ stdin(_stdout,_stderr)