ESP32 學習筆記(十八)Virtual filesystem
Virtual filesystem
Virtual filesystem component
概述
虛擬檔案系統(VFS)元件為可以對類檔案物件執行操作的驅動程式提供統一的介面。這可以是真實的檔案系統(FAT,SPIFFS 等),也可以是有檔案類介面的裝置驅動程式。
該元件允許 C 庫函式(如 fopen
和fprintf
)與 FS 驅動程式一起使用。在高層次,每個 FS 驅動程式與某些路徑字首相關聯。當其中一個 C 庫函式需要開啟檔案時,VFS 元件將搜尋與該檔案路徑關聯的 FS 驅動程式,並將該呼叫轉發給該驅動程式。VFS 還將給定檔案的讀取,寫入和其他呼叫轉發到同一 FS 驅動程式。
例如,可以使用 /fat
字首註冊 FAT 檔案系統驅動程式,並呼叫 fopen(“/fat/file.txt”,“w”)
。然後,VFS 元件將呼叫 FAT 驅動程式的 open
函式並將 /file.txt
引數傳遞給它(以及適當的模式標誌)。對返回的 FILE *
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 的方式,應使用 read
,write
等,或 read_p
,write_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 驅動程式可能有自己的檔名長度限制。
檔案描述符
檔案描述符是從 0
到 FD_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”,則 stdin
,stdout
和 stderr
配置為讀取和寫入 UART。可以將 UART0 或 UART1 用於標準 IO。預設情況下,使用 UART0,波特率為 115200,TX 引腳為 GPIO1,RX 引腳為 GPIO3。可以在 menuconfig 中更改這些引數。
寫入 stdout
或 stderr
會將字元傳送到 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_endings
和 esp_vfs_dev_uart_set_tx_line_endings
函式單獨為輸入和輸出配置它。
標準流和 FreeRTOS 任務
stdin
,stdout
和 stderr
的 FILE
物件在所有 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 啟動之前使用的結構)。
這樣的設計會產生以下後果:
- 可以為任何給定任務設定
stdin
,stdout
和stderr
,而不會影響其他任務,例如通過做stdin = fopen(“/dev/uart/1”,“r”)
。 - 使用 fclose 關閉預設
stdin
,stdout
或stderr
將關閉FILE
流物件 - 這將影響所有其他任務。 - 要更改新任務的預設
stdin
,stdout
,stderr
流,請在建立任務之前修改_GLOBAL_REENT - > _ stdin(_stdout,_stderr)
。