使用系统IO和标准IO的基本原理

系统 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 + 3lfs_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: %priu32n, 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 链接,或者发送 “测试代码” 到公众号后台获取源码。

A3只是起点,中国移动将牵手YunOS迈入5G时代
stm32mp1 Cortex M4开发篇8:扩展板LED灯控制实验
交错马刺:时序不匹配的数学
Silicon Labs无线收发器SI446x的应用技巧超详细讲解
全球5G套餐总览:人均月流量60G,最低资费30元/G
使用系统IO和标准IO的基本原理
美国开发移动式无线充电技术:实现电能无线传输,让汽车可以在行驶中充电
科大讯飞人脸比对方案 demo python3语言API文档
Littelfuse推出SR70系列瞬态抑制二极管阵列
TI针对移动电话与便携式电子产品推出充电速度更快、散热性能更
基于泰勒综合法的均匀阵列
联想集团副总裁放出了一张Z6 Pro状态栏的实拍图,让很多人惊呆了
基于PMBus的DC/DC转换器负载点解决方案
模拟仿真技术:SPICE常用的建模方法和步骤
洗衣机使用后八成存在病菌
小米6什么时候上市:小米6搭载国产处理器,缺货赔三!
一加5、小米6和荣耀9谁更值得买?一加5、小米6和荣耀9深度区别对比评测
对称半桥LLC电路原理讲解
25G SFP28有源光缆特征及应用分析
Xilinx-从FPGA到处理器