Rtems Source Code
Rtems 源码阅读
RTEMS(Real‑Time Executive for Multiprocessor Systems)是一款始于 1988 年、1993 年正式发布的开源实时操作系统,专为多处理器嵌入式环境设计,支持 POSIX 和 BSD 套接字等开放标准 API,并可运行于 ARM、PowerPC、SPARC、MIPS、RISC‑V 等 18 种处理器架构及近 200 个 BSP(Board Support Package)上。它以库形式发布,应用程序与内核静态链接为单一映像,采用单地址空间、无用户/内核隔离设计,从而简化资源管理并确保确定性响应。2025 年 1 月 22 日发布的 6.1 版本全面将构建系统由 GNU Autotools 切换到基于 Python 的 Waf,大幅提升了构建速度并优化了依赖管理,同时引入了改进的调度算法和增强的 SMP 支持。
本文章用于记录阅读 Rtems 内核源码的笔记,尝试理解其中的逻辑。Rtems 内核的版本是 6.1,在线代码网站见 https://rtems.davidingplus.cn/lxr/source/。
本文章中涉及到的源码摘抄见项目 DavidingPlus/rtems-source-code: Rtems 源码阅读。。
文件系统
系统调用
open()
open() 函数的调用流程图如下:
flowchart TD A[开始 open 函数] --> B[初始化变量 rv=0, mode=0, iop=NULL] B --> C[va_start 开始处理可变参数] C --> D[获取 mode 参数] D --> E{是否成功分配 iop} E -- 是 --> F[调用 do_open 打开文件] F --> G[设置 rv 为 do_open 返回值] E -- 否 --> H[设置 errno = ENFILE] H --> I[rv = -1] G --> J[va_end 结束可变参数处理] I --> J J --> K[返回 rv]
struct rtems_libio_t
rtems_libio_t 结构体定义如下。该结构体用于表示一个文件描述符的内部状态,Rtems 中每打开一个文件都会关联一个该结构体的实例,通常简称为 iop(I/O pointer)。
1 | typedef struct rtems_libio_tt rtems_libio_t; |
struct rtems_filesystem_location_info_t
rtems_filesystem_location_info_t 结构体定义如下。它表示一个路径位置,用于描述文件系统中某个具体节点(如文件或目录)的位置及其访问方式。
1 | // 表示文件系统中一个节点(如文件或目录)的位置及其访问信息。 |
struct rtems_filesystem_file_handlers_r
比较重要的成员是 const rtems_filesystem_file_handlers_r *handlers
,该结构类似于 Linux 内核中的 file_operations,定义如下:
1 | /** |
struct rtems_filesystem_mount_table_entry_tt
另一个成员是 struct rtems_filesystem_mount_table_entry_tt。这个结构体的作用是为每一个已挂载的文件系统提供一个集中式的描述,包含了文件系统的根节点信息、挂载点、类型、设备、访问控制状态等关键信息。对每个文件系统,Rtems 会维护一个这样的挂载表链表。在挂载和卸载文件系统时,Rtems 会对这个结构体进行相应的初始化、操作或释放。文件系统的挂载、查找路径、访问权限、卸载等都依赖于这个结构体中记录的信息。
1 | typedef struct rtems_filesystem_mount_table_entry_tt |
在 RTEMS 中,文件系统的操作由 rtems_filesystem_operations_table 结构体统一管理,它定义了路径解析、节点创建、删除、克隆等核心操作函数,作用上相当于 Linux 中的 inode_operations。每个挂载的文件系统通过 rtems_filesystem_mount_table_entry_t 表示,类似于 Linux 的 super_block,其中包含了指向操作表 ops 的指针。当用户发起如 open、read、write 等文件访问请求时,系统首先通过 eval_path_h 函数解析路径并定位到目标节点,然后使用该节点中挂载的 rtems_filesystem_file_handlers_r(类似 Linux 的 file_operations)来完成具体操作。整个设计将挂载管理、路径解析和文件操作职责分离,形成清晰的模块边界,同时借鉴了 Linux 文件系统架构的思想。
1 | struct _rtems_filesystem_operations_table |
rtems_libio_allocate()
open 函数中分配文件描述符结构使用的函数是 rtems_libio_allocate(),执行流程图如下:
flowchart TD A[开始 rtems_libio_allocate 函数] --> B[加锁保护空闲链表] B --> C[从空闲链表头获取 iop] C --> D{iop 是否为 NULL?} D -- 否 --> E[获取 iop->data1 到 next] E --> F[更新空闲链表头指针为 next] F --> G{next 是否为 NULL} G -- 是 --> H[更新尾指针为 &rtems_libio_iop_free_head] G -- 否 --> I[不操作] D -- 是 --> I H --> J[解锁释放访问] I --> J J --> K[返回 iop]
rtems_libio_iop_free_head
rtems_libio_iop_free_head 是一个全局变量,用于维护 Rtems 文件描述符(rtems_libio_t)的空闲链表头指针。
在初始化阶段,Rtems 会预分配一定数量的 rtems_libio_t 结构,并通过 data1 字段将它们串成一个单向链表。rtems_libio_iop_free_head 指向第一个可用节点。每次 rtems_libio_allocate() 被调用时,从头部取出一个节点,并更新链表。如果分配后链表为空,rtems_libio_iop_free_tail 会被指向 &rtems_libio_iop_free_head,表示空了。释放节点时会调用一个对应的 rtems_libio_free(iop),将节点重新挂回链表尾部。
那其实对于 rtems_libio_t 链表而言,在预分配的时候就需要将 rtems_filesystem_location_info_tt 中关于文件系统全局的 rtems_filesystem_file_handlers_r 和 rtems_filesystem_mount_table_entry_tt 信息写入,这样才能保证系统调用的时候能够成功调用底层函数。
初始化阶段的函数逻辑如下:
flowchart TD A[开始 rtems_libio_init 函数] --> B{rtems_libio_number_iops > 0?} B -- 否 --> Z[结束函数] B -- 是 --> C[设置空闲链表头指针] C --> D[初始化循环变量 i = 0] D --> E{i + 1 < rtems_libio_number_iops?} E -- 是 --> F[设置 iop->data1 = iop + 1] F --> G[i++, iop++] G --> E E -- 否 --> H[设置最后一个 iop->data1 = NULL] H --> I[设置链表尾指针 rtems_libio_iop_free_tail = &iop->data1] I --> Z
rtems_libio_iops 是 Rtems 预先分配的 I/O 控制块数组,配置了 CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 以后,会预先创建出这个数组。
问题来了:有了这个数组,为什么还要一个额外的 free list 链表来管理呢?
1 |
|
do_open()
open() 函数中分配好文件描述符结构以后,最终会到达 do_open() 函数的位置进行处理。
函数开始时,从 iop 获取文件描述符 fd,并根据 oflag 解析读写权限、创建、独占、截断和目录打开等标志。然后确定是否跟随符号链接,组合路径解析所需的权限标志 eval_flags。
接着初始化路径解析上下文,解析路径并获取当前文件系统位置。若支持创建普通文件且路径未结束,则调用创建文件的函数。随后判断是否以目录方式打开,并检查写权限和目录类型的合法性,防止写目录或以目录方式打开非目录。
路径信息保存到 iop->pathinfo,清理路径解析上下文后,设置文件控制标志,调用底层驱动的 open 函数打开文件。若成功且指定截断,则调用 ftruncate 截断文件内容,截断失败时关闭文件。
最后,若操作全部成功,设置文件打开标志并返回文件描述符;失败时释放资源并返回错误。整个过程确保了路径解析、权限检查和文件打开的正确性和安全性。
do_open() 函数的执行流程图如下:
flowchart TD A[开始 do_open] --> B[解析参数和标志] B --> C[路径解析启动: rtems_filesystem_eval_path_start] C --> D[获取 currentloc 和是否支持创建文件] D --> E{是否需要创建普通文件?} E -- 是 --> F[create_regular_file] E -- 否 --> G[跳过创建] F --> H[是否为目录打开或需要写权限?] G --> H H --> I{路径类型检查} I -- 错误: 写目录 --> J[设置 EISDIR 错误] I -- 错误: 非目录 --> K[设置 ENOTDIR 错误] I --> L[提取 pathinfo 并清理上下文] L --> M[设置 LibIO 打开标志] M --> N[调用底层 open 函数] N --> O{open 是否成功?} O -- 否 --> P[释放 iop 并返回 -1] O -- 是 --> Q{是否需要截断?} Q -- 是 --> R{是否有写权限?} R -- 否 --> S[errno = EINVAL, 返回错误] R -- 是 --> T[调用 ftruncate] T --> U{ftruncate 成功?} U -- 否 --> V[调用 close, 返回 -1] U -- 是 --> W[设置 LIBIO_FLAGS_OPEN, 返回 fd] Q -- 否 --> W S --> P V --> P
路径解析过程
do_open() 涉及到的路径解析代码片段如下:
1 | // 启动路径解析,准备解析文件路径。 |
rtems_filesystem_eval_path_start() 的执行流程图如下:
flowchart TD A[调用 rtems_filesystem_eval_path_start] --> B[调用 rtems_filesystem_eval_path_start_with_root_and_current] B --> C[清空上下文 memset] C --> D[设置 ctx 路径和长度] D --> E[设置 ctx 标志位] E --> F[初始化起始位置 set_startloc] F --> G[锁定起始位置 rtems_filesystem_instance_lock] G --> H[复制 startloc 到 currentloc] H --> I[继续解析路径 rtems_filesystem_eval_path_continue] I --> J[返回 ctx currentloc]
可以看出最后进入了 rtems_filesystem_eval_path_continue() 函数,嵌套太深了。目前看不懂整个路径的解析过程。
路径解析完毕后,do_open() 函数中执行以下函数用于维护状态:
1 | // 获取解析后的当前路径位置信息。 |
底层文件系统的 open 函数
拿到所有信息以后,do_open() 函数中调用底层文件系统的 open() 函数真正打开文件:
1 | // 调用底层文件系统的 open 函数打开文件。 |
close()
close() 函数的执行流程图如下。在前面更改完状态标志位后,还是会进入到底层文件系统的 close_h 函数。
flowchart TD A[开始关闭 fd] --> B{文件描述符是否越界} B -- 是 --> C[设置错误码坏文件描述符并返回失败] B -- 否 --> D[获取对应的 I/O 对象] D --> E[读取 I/O 对象标志] E --> F{标志中是否包含打开状态} F -- 否 --> G[设置错误码坏文件描述符并返回失败] F -- 是 --> H[清除引用计数部分] H --> I[构造去掉打开标志的新标志] I --> J[执行原子比较交换操作] J --> K{比较交换是否成功} K -- 是 --> L[继续关闭处理] K -- 否 --> M{标志中是否有非法状态} M -- 是 --> N[设置错误码设备忙并返回失败] M -- 否 --> F L --> O[调用文件系统的关闭函数] O --> P[释放 I/O 对象] P --> Q[返回关闭结果]
read()
read() 函数的执行流程图如下。可以看出除了做了一些检查以外,直接调用了底层文件系统的 read_h() 函数。
flowchart TD A[开始调用 read 函数] --> B[确认缓冲区指针有效] B --> C[确认读取长度合理] C --> D{尝试获取对应的 I/O 对象并检查是否可读} D -- 失败 --> E[设置错误码为坏文件描述符并返回失败] D -- 成功 --> F[调用底层读操作完成读取] F --> G[释放 I/O 资源] G --> H[返回读取到的字节数或者错误码]
write()
write() 函数的执行流程图如下。大致逻辑同样同 read 函数。
flowchart TD A[开始调用 write 函数] --> B[确认缓冲区指针有效] B --> C[确认写入字节数合理] C --> D{尝试获取对应的 I/O 对象并检查是否可写} D -- 失败 --> E[设置错误码坏文件描述符并返回失败] D -- 成功 --> F[调用底层写操作完成写入] F --> G[释放 I/O 资源] G --> H[返回写入的字节数或错误码]
文件系统启动流程
rtems_filesystem_initialize()
该函数用于初始化 Rtems 的根文件系统,通常是 IMFS。官方文档 提到,其他文件系统可以被挂载,但它们只能挂载到基础文件系统中的某个目录挂载点。对于我们想注册的自定义文件系统,有两种手段,一种是在根文件系统挂载好以后,找到某个目录手动挂载新文件系统,另一种是直接修改 rtems_filesystem_root_configuration 根文件系统的配置,使用我们自己的文件系统,这样 Rtems 在启动的时候就会默认跑我们自己的文件系统。
rtems_filesystem_initialize() 函数的执行流程图如下:
flowchart TD A[开始初始化文件系统] --> B[获取根文件系统挂载配置信息] B --> C[挂载根文件系统] C --> D{挂载是否成功} D -- 否 --> E[触发致命错误停止系统] D -- 是 --> F[创建 /dev 目录] F --> G{目录创建是否成功} G -- 否 --> H[触发致命错误停止系统] G -- 是 --> I[根文件系统和 /dev 目录创建完成] I --> J[说明其他文件系统需手动挂载] J --> K[初始化完成]
struct rtems_filesystem_mount_configuration
挂载根文件系统的挂载配置信息的结构体是 struct rtems_filesystem_mount_configuration。该结构体是 Rtems 中用于挂载文件系统时传递参数的配置结构。它的作用是将挂载一个文件系统所需的各种信息(如设备源、挂载点、文件系统类型等)集中在一起,作为参数传给 mount() 函数。
1 | typedef struct |
在 rtems_filesystem_initialize() 中,根文件系统的配置是预定义好的全局变量 rtems_filesystem_root_configuration。
1 | const rtems_filesystem_mount_configuration rtems_filesystem_root_configuration = { |
rtems_filesystem_register()
rtems_filesystem_register() 用于在 Rtems 操作系统中注册一个新的文件系统类型。它接收文件系统的类型名称和对应的挂载函数指针,动态分配内存创建一个文件系统节点,将类型名称和挂载函数保存到该节点中,并检查该类型是否已被注册。如果未注册,则将该节点添加到全局文件系统链表完成注册;如果已注册,则释放内存并返回错误。通过这个注册机制,系统能够识别和管理多种文件系统类型,并在需要时调用对应的挂载函数进行挂载操作。
rtems_filesystem_register() 的执行流程图如下:
flowchart TD A[开始 rtems_filesystem_register] --> B[计算 type_size 与 fsn_size 并 malloc 分配 fsn] B --> C{fsn 是否为 NULL?} C -- 是 --> D[设置 errno 等于 ENOMEM 并返回 -1] C -- 否 --> E[计算 type_storage 并 memcpy 复制类型字符串] E --> F[设置 fsn->entry.type 等于 type_storage] F --> G[设置 fsn->entry.mount_h 等于 mount_h] G --> H[rtems_libio_lock 进入临界区] H --> I{rtems_filesystem_get_mount_handler 判断 type 是否已注册} I -- 未注册 --> J[rtems_chain_initialize_node 初始化链表节点] J --> K[rtems_chain_append_unprotected 将 fsn 添加到全局链表] K --> L[rtems_libio_unlock 解锁] L --> M[返回 0 注册成功] I -- 已注册 --> N[rtems_libio_unlock 解锁] N --> O[free fsn 释放内存] O --> P[设置 errno 等于 EINVAL 并返回 -1]
在 rtems_filesystem_register() 的源码中,有几行比较细节的地方。在 Rtems 中,文件系统的注册全局表有两个,一个是静态表 rtems_filesystem_table,一个是动态表 filesystem_chain。静态表是 Rtems 预先定义好的常量,提供了 Rtems 内置的文件系统。动态表用于用户动态注册文件系统,在下面的函数实现中,如果发现全局表没有注册该文件系统,Rtems 会将该文件系统挂到 filesystem_chain 上。
好,问题来了,表里面除了 type 成员,更重要的是挂载函数 rtems_filesystem_fsmount_me_t。这个函数是需要我们自己写的,也就是说 Rtems 在注册文件系统的时候,除了记录到注册全局表以后,就没有其他架构上的操作了。后面的操作都需要我们自己做,Rtems 的文件系统虚拟层目前看起来几乎没有。
1 | rtems_chain_control *chain = &filesystem_chain; |
struct filesystem_node
struct filesystem_node 结构体的作用是将一个文件系统的描述信息封装为链表中的一个节点,使得多个文件系统表项可以通过链表的形式组织和管理。它结合了 Rtems 的链表节点结构 rtems_chain_node 与文件系统表项 rtems_filesystem_table_t,方便在系统中动态维护、查找和操作支持的文件系统。该结构体通常用于构建一个文件系统注册表,实现对多个文件系统的统一管理和遍历。
1 | // 定义一个结构体类型 filesystem_node,用于表示文件系统链表中的一个节点。 |
struct rtems_filesystem_table_t
struct rtems_filesystem_table_t 结构体的作用是描述一个可挂载的文件系统类型,包括文件系统的类型名称和对应的挂载函数。它为 RTEMS 提供了一种统一的方式来表示和管理不同类型的文件系统,使系统能够在运行时根据类型名称选择合适的挂载函数进行文件系统初始化和挂载操作。这种设计有助于扩展文件系统支持,并实现灵活的文件系统管理机制。
在 rtems_filesystem_register() 函数中,成员 type 和 mount_h 通过函数参数传入,并在函数内赋值。
1 | // 定义一个结构体类型 rtems_filesystem_table_t,用于描述一个可挂载的文件系统类型。 |
rtems_filesystem_fsmount_me_t
rtems_filesystem_fsmount_me_t 是一个函数指针,定义如下:
1 | /** |
struct rtems_filesystem_mount_table_entry_t 结构体的作用是为每一个已挂载的文件系统提供一个集中式的描述,包含了文件系统的根节点信息、挂载点、类型、设备、访问控制状态等关键信息。Rtems 会维护一个这样的挂载表链表,每个表项都是这个结构体的一个实例。在挂载和卸载文件系统时,Rtems 会对这个结构体进行相应的初始化、操作或释放。文件系统的挂载、查找路径、访问权限、卸载等都依赖于这个结构体中记录的信息。
关于 struct rtems_filesystem_mount_table_entry_tt 和 struct _rtems_filesystem_operations_table,前面在 struct rtems_libio_t 的时候提到过,不再赘述。
rtems_fsmount()
rtems_fsmount() 函数的作用是批量挂载多个文件系统,它按照用户提供的挂载表(fstab)顺序,依次创建每个挂载点目录并调用 mount() 执行挂载操作。该函数支持错误报告和失败控制机制,允许用户根据挂载结果决定是否继续处理后续项。通过这种方式,rtems_fsmount 提供了一种统一、高效的接口,适用于系统启动时自动挂载多个文件系统,简化了挂载流程并增强了可配置性。
rtems_fsmount() 的执行流程图如下:
flowchart TD A[开始, 初始化 rc, fstab_idx, terminate] --> B[检查循环条件 fstab_idx 小于 fstab_count, 且 terminate 为 false] B -->|是| C[创建挂载点, 调用 rtems_mkdir] C --> D{创建成功} D -- 否 --> E[根据配置报告错误, 更新 terminate 和 rc] D -- 是 --> F[执行挂载, 调用 mount] F --> G{挂载成功} G -- 否 --> H[根据配置报告挂载失败, 更新 terminate 和 rc] G -- 是 --> I[根据配置报告挂载成功, 更新 terminate] E & H & I --> J{terminate} J -- 否 --> K[fstab_ptr 前移, fstab_idx 加一, 然后返回 B] J -- 是 --> L[退出循环] B -->|否| L L --> M{fail_idx 是否存在} M -- 是 --> N[写入失败索引] M -- 否 --> O N --> P[返回 rc] O --> P
struct rtems_fstab_entry
struct rtems_fstab_entry 用于描述单个文件系统的挂载配置信息,包括挂载源设备或路径、挂载点目录、文件系统类型以及挂载时的选项和行为控制标志。它为系统批量挂载文件系统时提供了统一的数据格式,使挂载操作可以根据该结构体中的信息依次执行,支持对挂载过程中的错误报告和中止条件进行灵活管理。
1 | // 文件系统挂载表条目结构体。 |
mount()
mount() 函数用于根据指定的源路径、目标挂载点、文件系统类型和挂载选项,完成文件系统的挂载操作。它首先根据文件系统类型获取对应的挂载处理函数,然后创建一个挂载表项来保存挂载信息,调用具体文件系统的挂载函数进行挂载,并将挂载点注册到系统的文件系统层次中。如果挂载或注册失败,会进行相应的资源释放和错误处理。函数最终返回挂载结果,成功返回 0,失败返回 -1 并设置相应的错误码。
mount() 函数的执行流程图如下:
flowchart TD A[开始, 调用 mount] --> B[验证 options 是否为只读 或 读写] B -->|无效| C[设置 errno 为 EINVAL, 返回 -1] B -->|有效| D[调用 rtems_filesystem_get_mount_handler, 获取挂载函数] D -->|无 函数| E[设置 errno 为 EINVAL, 返回 -1] D -->|有 函数| F[调用 alloc_mount_table_entry, 分配挂载表项] F -->|分配 失败| G[设置 errno 为 ENOMEM, 返回 -1] F -->|分配 成功| H[根据 options 设置 writeable 标志] H --> I[调用挂载函数, 执行挂载] I -->|挂载 失败| J[释放挂载表项, 返回 -1] I -->|挂载 成功| K[注册 文件系统] K -->|注册 成功| L[返回 0] K -->|注册 失败| M[调用 fsunmount, 卸载, 释放挂载表项, 返回 -1]
IMFS 文件系统
IMFS(In‐Memory File System)是 Rtems 提供的一个内存文件系统。它将文件和目录全部存储在 RAM 中,为嵌入式应用提供快速、轻量级的 POSIX 风格文件操作接口;其核心模块负责管理目录树结构、路径解析和文件读写逻辑,而底层的节点分配与销毁、元数据初始化等则通过回调函数(如 node_initialize、node_remove、node_destroy)由系统默认实现或用户自定义实现来完成,使得 IMFS 在不修改主体框架的情况下能够灵活适配不同内存布局或特殊需求,启动时通过 IMFS_initialize 自动注册并挂载为根文件系统,用户即可直接使用标准的 open/read/write/close 等接口访问内存中存储的文件。
rtems_filesystem_get_mount_handler()
在 rtems_filesystem_register() 中,有一步是调用 rtems_filesystem_get_mount_handler 函数判断节点类型是否注册。这不仅让我们联想到,可能 Rtems 内部维护了一张全局表,专门用于记录目前已挂载的文件系统的信息。
rtems_filesystem_get_mount_handler() 的执行流程如下。
flowchart TD A[开始 rtems_filesystem_get_mount_handler] --> B{type 为空?} B -- 是 --> C[返回 NULL] B -- 否 --> D[遍历表 rtems_filesystem_table] D --> E{表项匹配?} E -- 是 --> F[返回对应 mount_h] E -- 否 --> G[遍历表 filesystem_chain] G --> H{表项匹配?} H -- 是 --> I[返回对应 mount_h] H -- 否 --> J[返回 NULL]
在 rtems_filesystem_iterate 函数中,可以发现 Rtems 定义了全局的两个表 filesystem_chain 和 rtems_filesystem_table。源码中无法直接跳转 filesystem_chain,目前无法得知他的具体状况。
1 | rtems_chain_control *chain = &filesystem_chain; |
variable rtems_filesystem_table
关于 rtems_filesystem_table,它用于表示挂载的文件系统的信息,并且猜测大概率是预挂载的文件系统的信息。结构体 rtems_filesystem_table_t 前面提到过,两个成员分别代表文件系统的类型名称 type 和文件系统的挂载函数指针 mount_h。
rtems_filesystem_table 的定义如下。其中关于 IMFS 有两个地方提到,一个是文件系统名字叫 “/“ 的文件系统,一个是下面的 IMFS 文件系统。他们对应的挂载函数分别叫 IMFS_initialize_support() 和 IMFS_initialize()。
1 | const rtems_filesystem_table_t rtems_filesystem_table[] = { |
IMFS_initialize() 和 IMFS_initialize_support()
IMFS_initialize() 的执行流程图如下:
flowchart TD A[开始 IMFS_initialize] --> B[调用 calloc 分配并清零 fs_info 内存] B --> C{fs_info 是否为 NULL} C -- 是 --> D[设置 errno 为 ENOMEM] --> E[返回 -1, 初始化失败] C -- 否 --> F[构造 IMFS_mount_data 结构体] F --> G[填入 fs_info 指针
填入文件系统操作集 &IMFS_ops
填入默认节点创建控制表 &IMFS_default_mknod_controls] G --> H[调用 IMFS_initialize_support 进行实际初始化和挂载] H --> I[返回初始化结果]
其中填充 IMFS_mount_data 结构体的代码截取如下,可以看出 rtems_filesystem_operations_table 结构体的回调函数是在这里被注册进去的。
1 | typedef struct |
IMFS_initialize() 最后会调用 IMFS_initialize_support(),IMFS_initialize_support() 的执行流程图如下:
flowchart TD A[开始 IMFS_initialize_support 函数] --> B[将 data 转为 IMFS_mount_data 指针] B --> C[提取 fs_info 和 mknod_controls] C --> D[获取 node_control 和 root_node 指针] D --> E[设置挂载点 mt_entry 的各项字段, 包括 fs_info, ops, pathconf, root 节点信息] E --> F[调用 IMFS_initialize_node 初始化根目录节点] F --> G[断言 root_node 不为 NULL] G --> H[返回 0, 表示初始化成功]
这其中也做了很多赋值操作,代码片段截取如下。其中 _rtems_filesystem_file_handlers_r 结构体的回调函数是在这里注册进去的。
1 | // 转换传入的 data 参数为具体类型。 |
IMFS_initialize_node()
IMFS_initialize_support() 最后会调用 IMFS_initialize_node() 函数。该函数用于初始化一个 IMFS 节点(内存文件系统中的目录或文件节点)。根据传入的信息填充节点结构体的基本字段,并调用节点类型控制器提供的初始化回调。成功返回已初始化的节点指针,失败返回 NULL 并设置 errno。
IMFS_initialize_node() 的执行流程图如下:
flowchart TD A[开始 IMFS_initialize_node 函数] --> B{namelen 是否超过 IMFS_NAME_MAX} B -- 是 --> C[设置 errno 为 ENAMETOOLONG
返回 NULL] B -- 否 --> D[填充节点基本字段 name, namelen, reference_count, st_nlink, control] D --> E[设置权限和属主信息 mode, uid, gid] E --> F[获取当前时间 now] F --> G[设置时间戳 atime, mtime, ctime] G --> H[调用 node_control 的 node_initialize 回调] H --> I[返回初始化后的节点指针]
该函数在设置了节点 node 的信息以后,会调用 node_control 结构体的 node_initialize 回调函数进行 node 初始化。代码片段如下:
1 | // 调用节点控制器中定义的初始化函数以执行具体类型的初始化。 |
struct IMFS_jnode_tt
IMFS_jnode_tt 是 IMFS 自己设计的 inode 结构,作用和 Linux 中文件系统自己的 inode 结构效果相同,定义如下:
1 | // IMFS_jnode_tt 是 IMFS 文件系统中用于表示一个文件或目录的节点结构体。这是内存文件系统(IMFS)中最核心的数据结构之一,包含名称、权限、所有者、时间戳等元数据,以及指向父节点和控制操作的指针。 |
struct IMFS_node_control
struct IMFS_jnode_tt 中除了 inode 的基本信息以外,需要关注的一点就是 struct IMFS_node_control。该结构体定义了 IMFS inode 节点的操作函数,并作统一控制管理。
1 | /** |
前面提到,_rtems_filesystem_file_handlers_r 结构体的回调函数是在 IMFS_initialize_support() 中注册的,最终就来源于 IMFS_node_control 中的 rtems_filesystem_file_handlers_r *handlers。
1 | mt_entry->mt_fs_root->location.handlers = node_control->handlers; |
下一步就是找到这些函数最开始是在哪里被注册的。最终在 cpukit/libfs/src/imfs/imfs_linfile.c 中找到了答案:
1 | static const rtems_filesystem_file_handlers_r IMFS_linfile_handlers = { |