系統 IO 和標準 IO
系統 IO 一般指的是 Linux/Unix 系統調用中關于 I/O 操作的統稱,其中包括 open、read、write、close 等操作。
與系統 IO 對應還有標準 IO,標準 IO 是 ISO 標準中 C 語言標準定義的 IO 訪問接口,例如 fprintf/fgets 等 C 語言標準中定義的文件訪問接口。
在 Linux 系統中 open/read/write 等函數的底層實現是通過系統調用訪問的,在 STM32 的裸機中沒有操作系統,更沒有這些系統調用。
但是我們可以用一種其他的方式去實現這些系統 IO,而不需要操作系統。
半主機模式重寫文件訪問接口
這個方法其實就是利用半主機模式,去重寫系統庫中關于半主機接口中關于文件訪問接口的底層 "弱定義" 。
這個聽上去好像挺陌生的,其實很多人都使用過,就是最簡單的 printf 重定向。
在 GCC 重定向 printf 到串口使用了如下代碼:
int _write(int fd, char * ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t *) ptr, len, HAL_MAX_DELAY);
return len;
}
這個就是在半主機模式下重寫了 write 函數的底層接口,當系統調用 printf 函數時最終會調到 _write 函數向串口寫入數據。
在 ARM 關于主機模式的文檔 中,Direct semihosting C library function dependencies 一節提供了可重寫的系統 IO 的底層函數。
通過重寫上述列表中的函數,即可通過調用 C 庫 系統 IO 訪問。
構建文件系統
在上面介紹使用系統 IO 的基本原理:通過重寫 _open/_write/_read 等接口,即可通過 open/write/read 接口訪問。
但是以上只提供了一系列系統接口,并將其與標準 IO 綁定,可以使用 open/fopen 等函數進行訪問,但是具體訪問的數據依舊需要自己進行實現。
在這次測試中我選用了 LittleFS 作為文件系統,使用 RAM 中預分配的全局變量作為存儲介質,構建了一個基于內存的文件系統。(開發板沒有 Flash 先用 RAM 代替了。。。)
其 _open 函數如下:
// 文件描述符列表,不包括標準輸入輸出, 最大 fd 為 FS_FILE_MAX + 3
lfs_file_t *g_file_list[FS_FILE_MAX] = {0};
int _open(const char *name, int flags)
{
int i;
int i_flags = 0;
if ((flags & O_CREAT) == O_CREAT) i_flags |= LFS_O_CREAT;
if ((flags & O_RDONLY) == O_RDONLY) i_flags |= LFS_O_RDONLY;
if ((flags & O_WRONLY) == O_WRONLY) i_flags |= LFS_O_WRONLY;
if ((flags & O_RDWR) == O_RDWR) i_flags |= LFS_O_RDWR;
for (i = 0; i < FS_FILE_MAX; i++)
{
if (g_file_list[i] == NULL)
{
g_file_list[i] = malloc(sizeof(lfs_file_t));
lfs_file_open(&g_lfs, g_file_list[i], name, i_flags);
return i + 3;
}
}
return -1;
}
其基本邏輯是將 open 傳入的參數轉換為 lfs_file_open 使用的參數,傳入 lfs_file_oen, 然后分配一個空閑的文件描述符作為返回值。
在 _read 和 _write 接口中對文件描述符進行判斷,當文件描述符為 0/1/2 時將數據重定向到串口,否則從文件中讀寫數據。代碼如下:
int _write(int fd, char *pBuffer, int size)
{
int res = 0;
if (fd == 1 || fd ==2)
{
HAL_UART_Transmit(&huart3, (uint8_t *)pBuffer, size, size);
}
else
{
res = lfs_file_write(&g_lfs, g_file_list[fd], pBuffer, size);
}
return res;
}
完成以上步驟后,便可以在程序中使用 open/read/write 等接口訪問文件系統了,測試程序如下:
fs_init();
write(STDOUT_FILENO, "system init ...n", 17);
mkdir("/data", 0755);
fd = open("/data/ascii.txt", O_CREAT|O_WRONLY);
for (ch = 32; ch < 126; ch++)
{
write(fd, &ch, 1);
}
close(fd);
fd = open("/data/ascii.txt", O_RDONLY);
while (1)
{
char buff[16];
int res = read(fd, buff, 16);
if (res < 0)
{
close(fd);
break;
}
printf("system tick: %"PRIu32"n", HAL_GetTick());
printf("read file data:%.*sn", 16, buff);
HAL_Delay(500);
}
程序下載燒錄后,使用串口工具查看到以下數據:
移植的用途
關于在 STM32 中使用系統 IO 的嘗試,主要是為了在 STM32 上移植一些 Linux 下的第三方庫。
他們很多都不可避免的使用了文件 IO 和 Posix 線程接口,對于 Posix 線程的接口在 FreeRTOS 中有提供,但是系統 IO 卻沒有找到什么合適的方案,于是有了這樣的一種嘗試。
現在好像已經有了更好的方案而不用去移植,不過使用這種方式的好處是以較少的代碼可以將系統 IO 和標準 IO 進行關聯。
關于半主機模式
最后提一下半主機模式:這個實質上是提供了一個在調試時訪問主機數據的方法:
通過觸發 SVC 指令,在 R0 寄存器中傳入需要的系統調用 ID, 在 R1 寄存器中傳入參數結構體的指針。
通過調試器,可以在主機接受到對應的系統調用,并進行相應的處理。
該測試程序整理好后,上傳到文末 閱讀原文 的 github 鏈接,或者發送 “測試代碼” 到公眾號后臺獲取源碼。
-
接口
+關注
關注
33文章
8496瀏覽量
150834 -
Linux
+關注
關注
87文章
11225瀏覽量
208917 -
操作系統
+關注
關注
37文章
6737瀏覽量
123190 -
STM32
+關注
關注
2266文章
10871瀏覽量
354789 -
IO口
+關注
關注
3文章
169瀏覽量
23994
發布評論請先 登錄
相關推薦
評論