系統(tǒng) IO 和標(biāo)準(zhǔn) IO
系統(tǒng) IO 一般指的是 Linux/Unix 系統(tǒng)調(diào)用中關(guān)于 I/O 操作的統(tǒng)稱(chēng),其中包括 open、read、write、close 等操作。
與系統(tǒng) IO 對(duì)應(yīng)還有標(biāo)準(zhǔn) IO,標(biāo)準(zhǔn) IO 是 ISO 標(biāo)準(zhǔn)中 C 語(yǔ)言標(biāo)準(zhǔn)定義的 IO 訪問(wèn)接口,例如 fprintf/fgets 等 C 語(yǔ)言標(biāo)準(zhǔn)中定義的文件訪問(wèn)接口。
在 Linux 系統(tǒng)中 open/read/write 等函數(shù)的底層實(shí)現(xiàn)是通過(guò)系統(tǒng)調(diào)用訪問(wèn)的,在 STM32 的裸機(jī)中沒(méi)有操作系統(tǒng),更沒(méi)有這些系統(tǒng)調(diào)用。
但是我們可以用一種其他的方式去實(shí)現(xiàn)這些系統(tǒng) IO,而不需要操作系統(tǒng)。
半主機(jī)模式重寫(xiě)文件訪問(wèn)接口
這個(gè)方法其實(shí)就是利用半主機(jī)模式,去重寫(xiě)系統(tǒng)庫(kù)中關(guān)于半主機(jī)接口中關(guān)于文件訪問(wèn)接口的底層 "弱定義" 。
這個(gè)聽(tīng)上去好像挺陌生的,其實(shí)很多人都使用過(guò),就是最簡(jiǎn)單的 printf 重定向。
在 GCC 重定向 printf 到串口使用了如下代碼:
int _write(int fd, char * ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t *) ptr, len, HAL_MAX_DELAY);
return len;
}
這個(gè)就是在半主機(jī)模式下重寫(xiě)了 write 函數(shù)的底層接口,當(dāng)系統(tǒng)調(diào)用 printf 函數(shù)時(shí)最終會(huì)調(diào)到 _write 函數(shù)向串口寫(xiě)入數(shù)據(jù)。
在 ARM 關(guān)于主機(jī)模式的文檔 中,Direct semihosting C library function dependencies 一節(jié)提供了可重寫(xiě)的系統(tǒng) IO 的底層函數(shù)。
通過(guò)重寫(xiě)上述列表中的函數(shù),即可通過(guò)調(diào)用 C 庫(kù) 系統(tǒng) IO 訪問(wèn)。
構(gòu)建文件系統(tǒng)
在上面介紹使用系統(tǒng) IO 的基本原理:通過(guò)重寫(xiě) _open/_write/_read 等接口,即可通過(guò) open/write/read 接口訪問(wèn)。
但是以上只提供了一系列系統(tǒng)接口,并將其與標(biāo)準(zhǔn) IO 綁定,可以使用 open/fopen 等函數(shù)進(jìn)行訪問(wèn),但是具體訪問(wèn)的數(shù)據(jù)依舊需要自己進(jìn)行實(shí)現(xiàn)。
在這次測(cè)試中我選用了 LittleFS 作為文件系統(tǒng),使用 RAM 中預(yù)分配的全局變量作為存儲(chǔ)介質(zhì),構(gòu)建了一個(gè)基于內(nèi)存的文件系統(tǒng)。(開(kāi)發(fā)板沒(méi)有 Flash 先用 RAM 代替了。。。)
其 _open 函數(shù)如下:
// 文件描述符列表,不包括標(biāo)準(zhǔn)輸入輸出, 最大 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 傳入的參數(shù)轉(zhuǎn)換為 lfs_file_open 使用的參數(shù),傳入 lfs_file_oen, 然后分配一個(gè)空閑的文件描述符作為返回值。
在 _read 和 _write 接口中對(duì)文件描述符進(jìn)行判斷,當(dāng)文件描述符為 0/1/2 時(shí)將數(shù)據(jù)重定向到串口,否則從文件中讀寫(xiě)數(shù)據(jù)。代碼如下:
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 等接口訪問(wèn)文件系統(tǒng)了,測(cè)試程序如下:
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);
}
程序下載燒錄后,使用串口工具查看到以下數(shù)據(jù):
移植的用途
關(guān)于在 STM32 中使用系統(tǒng) IO 的嘗試,主要是為了在 STM32 上移植一些 Linux 下的第三方庫(kù)。
他們很多都不可避免的使用了文件 IO 和 Posix 線程接口,對(duì)于 Posix 線程的接口在 FreeRTOS 中有提供,但是系統(tǒng) IO 卻沒(méi)有找到什么合適的方案,于是有了這樣的一種嘗試。
現(xiàn)在好像已經(jīng)有了更好的方案而不用去移植,不過(guò)使用這種方式的好處是以較少的代碼可以將系統(tǒng) IO 和標(biāo)準(zhǔn) IO 進(jìn)行關(guān)聯(lián)。
關(guān)于半主機(jī)模式
最后提一下半主機(jī)模式:這個(gè)實(shí)質(zhì)上是提供了一個(gè)在調(diào)試時(shí)訪問(wèn)主機(jī)數(shù)據(jù)的方法:
通過(guò)觸發(fā) SVC 指令,在 R0 寄存器中傳入需要的系統(tǒng)調(diào)用 ID, 在 R1 寄存器中傳入?yún)?shù)結(jié)構(gòu)體的指針。
通過(guò)調(diào)試器,可以在主機(jī)接受到對(duì)應(yīng)的系統(tǒng)調(diào)用,并進(jìn)行相應(yīng)的處理。
該測(cè)試程序整理好后,上傳到文末 閱讀原文 的 github 鏈接,或者發(fā)送 “測(cè)試代碼” 到公眾號(hào)后臺(tái)獲取源碼。
-
接口
+關(guān)注
關(guān)注
33文章
8691瀏覽量
151919 -
Linux
+關(guān)注
關(guān)注
87文章
11345瀏覽量
210396 -
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
6895瀏覽量
123745 -
STM32
+關(guān)注
關(guān)注
2272文章
10924瀏覽量
357577 -
IO口
+關(guān)注
關(guān)注
3文章
170瀏覽量
24200
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
標(biāo)準(zhǔn)IO的介紹
GPIO基本原理與寄存器配置基礎(chǔ)信息
文件IO與標(biāo)準(zhǔn)IO有何區(qū)別
搞懂文件IO與標(biāo)準(zhǔn)IO
電源濾波器的基本原理和常用標(biāo)準(zhǔn)
AVR的IO結(jié)構(gòu)分析與操作
變跨導(dǎo)乘法器的基本原理
![變跨導(dǎo)乘法器的<b class='flag-5'>基本原理</b>](https://file1.elecfans.com//web2/M00/A5/99/wKgZomUMOSuANFMmAABYs3QO7_s493.jpg)
網(wǎng)絡(luò)監(jiān)控的基本原理和標(biāo)準(zhǔn)介紹
如何使用io.Reader和io.Writer接口在程序中實(shí)現(xiàn)流式IO
嵌入式Linux開(kāi)發(fā)系統(tǒng)開(kāi)發(fā)之《一節(jié)課搞懂文件IO與標(biāo)準(zhǔn)IO》
![嵌入式Linux開(kāi)發(fā)<b class='flag-5'>系統(tǒng)</b>開(kāi)發(fā)之《一節(jié)課搞懂文件<b class='flag-5'>IO</b>與<b class='flag-5'>標(biāo)準(zhǔn)</b><b class='flag-5'>IO</b>》](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
怎樣將IO設(shè)備分配給IO控制器?
使用STM32F10xxx SWJ引腳作為標(biāo)準(zhǔn)IO
多路IO復(fù)用模型和異步IO模型介紹
![多路<b class='flag-5'>IO</b>復(fù)用模型和異步<b class='flag-5'>IO</b>模型介紹](https://file1.elecfans.com/web2/M00/A9/0F/wKgZomUic4qAaXMRAAdcO5ckngg866.jpg)
信號(hào)驅(qū)動(dòng)IO與異步IO的區(qū)別
![信號(hào)驅(qū)動(dòng)<b class='flag-5'>IO</b>與異步<b class='flag-5'>IO</b>的區(qū)別](https://file1.elecfans.com/web2/M00/AD/0A/wKgaomVLOViAbmdBAAEigrzShcg767.jpg)
電流倒灌揭秘:IO口損壞與系統(tǒng)故障的真相
![電流倒灌揭秘:<b class='flag-5'>IO</b>口損壞與<b class='flag-5'>系統(tǒng)</b>故障的真相](https://file.elecfans.com/web2/M00/50/DA/pYYBAGLH6TyAB71EAAAPQ7KgtYA038.png)
評(píng)論