31、【OS】【Nuttx】OSTest分析(1):stdio测试(一)
背景
接上篇wiki
30、【OS】【Nuttx】构建脚本优化,引入待构建项目参数
最小系统分析完后,下一个能够更全面了解Nuttx的Demo,当然选择OSTest,里面有大量关于OS的测试用例,方便对Nuttx的整体功能有个把握。
stdio_test
OSTest 首个测试用例,向标准输出端口写入一串数据
这里 write 函数原型如下,其中输入参数为
- fd:file description,文件描述符(就是文件id,在Nuttx文件系统中,所有设备包括输入输出端口被视为文件)
- buf:数据指针
- nbytes:数据长度
所以上面的测试用例为向 fd = 1 和 fd = 2 的文件写入一串字符,write_data 定义如下:
在 Nuttx 或类Unix系统(如Linux),文件描述符 1 和 2 是标准 IO 流的预定义文件描述符,分别对应标准输出(stdout)和标准错误(stderr)
- 文件描述符 0 (stdin):标准输入流
- 文件描述符 1 (stdout):标准输出流,通常用于向终端或控制台输出信息或程序正常运行结果。
- 文件描述符 2 (stderr):标准错误流,通常用于输出错误信息或警告消息。与 stdout 不同的是,stderr 一般不会被重定向,因此即使 stdout 被重定向到文件或其他地方,错误信息仍然会显示在终端上。
综上,stdio_test 的测试目的是往标准输出端口和标准错误端口写一串数据
另外,write 函数接口处于文件系统的 VFS 抽象层(Virtual File System),即虚拟文件系统,VFS 是 Nuttx 文件系统中的一个重要组成部分,VFS 位于用户应用程序和实际文件系统实现之间,为用户提供了统一接口来访问不同文件系统类型,而无需关心底层存储介质的具体细节
标准IO端口初始化
首先来简单介绍文件系统中几个关键类型,首先是 filelist 队列,里面存放了各文件实例,当用户拥有文件描述符后,可以从 filelist 队列中取出对应的文件实例,以便对文件进行操作
下面来详细分析下这个 filelist 队列结构体,里面有五个成员,其中最关键的为 fl_files 成员,该成员是一个指针,指向一个二维数组,这个二维数组里直接存放了所有文件实例
- fl_lock:全局原子锁,防止资源竞争
- fl_rows:fl_files的行数,表示有多少块文件实例,文件系统每次会预先申请一块内存区域,用来预存放即将注册的文件实例,每申请一次,fl_rows加1
- fl_files:filelist 队列的核心成员,指向存放所有文件实例的二维数组,当用户通过文件描述符对文件实例进行访问时,fd / NFILE_DESCRIPTORS_PER_BLOCK 为二维数组的行索引,fd % NFILE_DESCRIPTORS_PER_BLOCK 为二维数组的列索引(这里NFILE_DESCRIPTORS_PER_BLOCK 就是文件系统的块大小),此时文件系统可以很容易找出文件描述符 fd 对应的实例进行使用
- fl_prefile:预存放文件实例的内存指针
- fl_prefiles:预存放文件实例的内存
filelist作用如下图
SIM 中每个块能放8个文件实例
这里思考个问题,为什么这里 fl_files 使用二维数组,而不是一维数组?有几个关键原因,主要与内存管理和性能优化有关:
- 分块管理:通过将文件描述符按块组织,可以更高效地管理内存分配和释放。每个块大小由宏 CONFIG_NFILE_DESCRIPTORS_PER_BLOCK 定义,在二维数组 fl_files 中每一行代表一个固定大小的块,可以一次性分配或释放一整块资源。分块管理后,有如下几个显著的优点:
1、减少碎片:相比于逐个分配文件描述符,分块分配可以减少内存碎片
2、简化内存管理:当需要扩展时,只需增加新的块而不是逐个增加文件描述符
3、提高缓存命中率:同一块中的文件描述符在内存中是连续存储的,访问同一块内的多个文件描述符有利于提高缓存命中率,从而提升性能 - 预分配和线程安全:通过预先分配一定数量的文件描述符(如 fl_prefiles 数组),可以在创建线程时避免动态内存分配,确保系统的功能安全性,这也是 filelist 队列结构体类型定义时注释的
- 简化锁定机制:由于每个块大小是固定的,锁定机制可以更加精细。比如可以给每个块设置单独的锁,或者在整个 filelist 上设置一个全局锁(如 fl_lock),以确保多线程环境下的数据一致性
先分析到这里,下次再接着分析