当前位置: 首页 > web >正文

[vale os_3] 文件系统/VFS | 网络协议栈

第四章:文件系统 / VFS

欢迎回来!

  • 在第一章:Kconfig中,我们学习了如何为openvela系统选择组件
  • 在第二章:构建系统中,我们看到了这些选择如何被构建成可运行的软件
  • 在第三章:驱动程序中,我们探索了称为驱动程序的特定软件模块如何让操作系统与串口、LED或传感器等独立硬件组件通信,通常使它们以特殊"设备节点"的形式出现在/dev目录中。

现在,我们将深入探讨管理这些设备节点和处理存储设备上传统文件的系统:文件系统,特别是通过 虚拟文件系统(VFS)层。


什么是文件系统 / VFS?统一管理器

想象你的嵌入式系统有多个可能找到"数据"的地方:

  • 某些数据来自硬件设备,如报告温度的传感器。
  • 某些数据需要发送到设备,如控制电机的命令。
  • 其他数据可能永久存储在闪存芯片SD卡上,如配置文件、日志或保存的应用程序数据。

传统上,与传感器交互、控制电机和从SD卡读取文件都需要完全不同的函数集和编程方法。这将使应用程序开发变得非常复杂。

虚拟文件系统(VFS)层协调的文件系统通过提供统一方式来访问不同类型的数据源和目的地解决了这个问题。

将VFS视为通用接待员图书馆目录与管理系统

  • 它提供单一的标准化接口openreadwriteclose等),应用程序无论与硬件设备通信还是从存储设备读取文件都可以使用。
  • 就像图书馆目录告诉你在哪里找到书籍(主书库、特藏等),VFS知道将请求路由到哪里:是给驱动程序管理的设备,还是给特定存储文件系统管理的文件?
  • 它将所有可访问的内容组织成单一的层次树结构(如计算机上的文件夹和文件)。

这种设备和文件使用相同函数集进行访问的方法,通常被称为类Unix操作系统中的**“万物皆文件”**哲学。

核心理念:通过路径访问万物

核心概念是openvela中大多数交互资源(硬件设备、数据文件)都可以通过文件系统类层次结构中的路径访问,从根目录(/)开始:

  • 硬件设备: 如第三章:驱动程序所示,驱动程序管理的设备通常显示为称为设备节点的特殊条目,通常位于/dev目录下(例如/dev/uart0/dev/led1/dev/temp_sensor)。
  • 真实文件: 存储在物理存储(如Flash或SD卡)上的文件出现在该存储文件系统"挂载"的路径下(例如/var/log.txt/mnt/sdcard/photo.jpg)。
  • 特殊系统信息: 类似Linux的/proc,openvela可能通过"伪"文件系统暴露系统信息(不过在基本配置中不如/dev突出)。

应用程序在调用open("/dev/uart0", ...)open("/var/config.txt", ...)时不需要知道区别。VFS处理将请求路由到正确的底层处理器。

虚拟文件系统(VFS):路由器和标准化器

**虚拟文件系统(VFS)**是实现这一可能的软件层。它位于应用程序使用的标准系统调用(如open()read()write()close())和实际处理请求的代码(驱动程序或特定存储文件系统)之间。

在这里插入图片描述

当应用程序在文件描述符上调用read()时:

  • VFS接收调用。
  • 查找文件描述符以确定对应的底层资源。
  • 如果是设备节点(如/dev/uart0),VFS调用UART驱动程序实现的read函数。
  • 如果是普通文件(如/var/log.txt),VFS调用FAT文件系统驱动实现的read函数(假设/var挂载为FAT分区)。

VFS隐藏了细节,提供一致的编程接口

VFS和驱动程序管理的设备节点

我们在第三章:驱动程序中提到了设备节点。让我们回顾VFS如何与它们交互。

当驱动程序初始化时(通常在系统启动期间),它调用register_driver()在VFS层次结构中创建设备节点,通常在/dev下。

// 简化的驱动程序注册示例
#include <nuttx/fs/fs.h> // 包含register_driver// 假设led_fops是LED驱动的struct file_operations
extern const struct file_operations led_fops;int led_driver_init(...) {// ... 驱动程序硬件设置 ...// 在/dev/led0注册驱动程序int ret = register_driver("/dev/led0", &led_fops, 0666, NULL);if (ret < 0) {// 处理错误return ret;}return 0;
}

这个调用告诉VFS:“嗨,在/dev/led0有一个设备,当有人尝试对该路径进行openreadwrite等操作时,你应该调用这些函数(&led_fops)”。

当应用程序稍后调用open("/dev/led0", O_WRONLY)时,VFS:

  1. 在树结构中查找路径/dev/led0
  2. 找到由register_driver创建的条目。
  3. 看到该条目与led_fops结构关联。
  4. 分配一个struct file来表示这个打开的实例。
  5. 调用led_fops结构中的open函数指针,传递struct file
  6. 向应用程序返回文件描述符(fd),本质上是应用程序打开struct file实例列表的索引。

后续的write(fd, ...)等调用将类似地由VFS路由到与该fd关联的struct file中存储的led_fops结构中的write函数指针。


真实文件系统:管理存储

除了设备节点,VFS还管理驻留在物理存储设备(如闪存(NAND/NOR)或可移动介质(SD卡))上的真实文件系统

这些存储设备通常通过块驱动程序访问。块驱动程序提供在设备上以固定大小"块"或"扇区"读写数据的标准方式,隐藏底层硬件细节。VFS使用struct block_operations接口(在file_system.md片段中简要提到)与块驱动程序交互。

存储文件系统(如FAT、ROMFS等)是知道如何在块设备提供的块内组织文件和目录的软件。它管理:

  • 目录结构(文件夹)
  • 文件名和权限
  • 跟踪哪些块属于哪些文件
  • 空闲空间管理

openvela支持的文件系统示例(通过Kconfig启用):

  • ROMFS: 简单的只读文件系统,用于构建时存储在ROM/Flash中的数据。
  • FAT(FAT12、FAT16、FAT32): 广泛兼容的SD卡和U盘文件系统。
  • LittleFS: 专为小型SPI NOR闪存设计的日志文件系统。
  • EXFAT: 常用于较大SD卡和U盘的文件系统。
  • TMPFS: 将一切存储在RAM中的文件系统(临时存储)。
  • NXFFS: 较旧的NuttX专用NOR闪存磨损均衡文件系统。

挂载文件系统:将存储附加到树

要访问存储设备上的文件,必须将存储文件系统挂载到VFS树的特定目录路径(称为挂载点)。

使用mount()系统调用(如file_system.md片段所示)完成此操作:

#include <sys/mount.h> // 包含mount()// 示例:将块设备/dev/mmcsd0上的FAT文件系统挂载到VFS树的/mnt/sdcard路径
int mount_sdcard() {const char *source = "/dev/mmcsd0"; // 块设备路径const char *target = "/mnt/sdcard"; // VFS树中的路径(挂载点)const char *filesystemtype = "vfat"; // 文件系统类型(FAT)unsigned long mountflags = 0; // 标准标志const void *data = NULL; // 可选的特定文件系统数据// 确保目标目录存在// mkdir("/mnt/sdcard", 0777); // 通常需要先创建int ret = mount(source, target, filesystemtype, mountflags, data);if (ret < 0) {perror("挂载失败"); // 打印错误信息return -1;}printf("成功将%s挂载到%s\n", source, target);return 0;
}

mount()期间发生了什么?

参考file_system.md片段中的mount()描述和关键代码:

  1. 调用mount()函数,参数包括块设备路径(source)、VFS树中的目标路径(target)和filesystemtype
  2. VFS查找filesystemtype(“vfat”)以找到FAT文件系统驱动对应的struct mountpt_operations(使用片段中的g_bdfsmap映射)。
  3. 查找source路径(/dev/mmcsd0)以找到表示块设备驱动的inode
  4. 查找或创建target路径(/mnt/sdcard)的inode。如果不存在,则调用inode_reserve。如果存在,则检查是否为有效目录/挂载点。
  5. 关键步骤:调用FAT文件系统的struct mountpt_operations中的bind函数。bind函数初始化文件系统软件,从块设备(/dev/mmcsd0)读取其结构,并准备进行文件操作。它可能存储文件系统特定数据(如缓存的目录信息)并返回handle
  6. VFS更新/mnt/sdcardinode,将其标记为挂载点(INODE_SET_MOUNTPT),并在i_private字段中存储FAT文件系统的struct mountpt_operationsbind返回的handle

成功挂载后,任何以/mnt/sdcard开头的VFS操作(如openreaddir)将被路由到已挂载FAT文件系统驱动的struct mountpt_operations函数。


伪文件系统:基础

即使没有物理存储挂载,openvela也有基本的基于内存的伪文件系统。通过CONFIG_NFILE_DESCRIPTORS > 0启用:

  • 提供根目录(/)。
  • 通常是驱动程序注册设备节点(/dev/...)的位置。
  • 包含/dev/var/tmp/mnt等目录的inode,这些目录作为真实文件系统的潜在挂载点。

这个伪文件系统为VFS树提供了基本结构,即使没有持久存储。

关键数据结构(简化版)

理解VFS涉及几个核心数据结构(在file_system.md片段中引用):

  1. struct inode:表示VFS树中的任何内容——目录、普通文件、字符设备、块设备、挂载点等。层次结构中的每个条目都有一个inode

    • i_peeri_child:构建目录树的链接。
    • i_crefs:引用计数(该节点被使用的次数)。
    • i_flags:指示类型(目录、驱动、挂载点等)。
    • u:包含指向适合i_flags的操作(file_operationsblock_operationsmountpt_operations)的联合体。
    • i_private:私有数据指针,通常指向驱动程序的内部状态或bind后的文件系统句柄。
    • i_name:在父目录中的条目名称。
  2. union inode_ops_ustruct inode中的联合体,保存可操作该节点类型的函数指针。VFS使用i_flags确定联合体中哪个指针有效以及使用哪组操作。

    • i_ops:指向字符驱动的struct file_operations
    • i_bops:指向块驱动的struct block_operations
    • i_mops:指向挂载点的struct mountpt_operations(处理文件系统操作)。
  3. struct file_operations字符设备和某些伪文件的标准接口。包含openclosereadwriteioctl等函数指针(参见第三章:驱动程序和片段)。

  4. struct mountpt_operations已挂载文件系统的标准接口。包含类似file_operations的函数(针对挂载FS内打开文件的openclosereadwriteioctl),加上文件系统特定的函数如bindunbindopendirreaddirstatmkdirunlink等(参见片段)。

  5. struct file:表示特定任务打开的文件或设备节点实例。当应用程序调用open()时,VFS分配struct file

    • f_oflags:打开时使用的标志(只读、只写等)。
    • f_pos:文件/设备中的当前读写位置。
    • f_inode:指回该struct file实例访问的struct inode
    • f_priv:此特定打开实例的私有数据(与inodei_private不同)。

每个任务都有struct file指针列表(struct filelist),open()返回的文件描述符(fd)本质上是该列表的索引。

流程:从应用到实现

让我们追踪read()调用的工作流程,取决于它是针对设备还是存储上的文件。

在这里插入图片描述

该图展示了VFS(使用inode结构和关联的操作集)如何作为中央调度器,根据路径将请求路由到驱动或特定文件系统实现。

Kconfig与文件系统

与驱动程序类似,文件系统支持使用Kconfig配置(第一章:Kconfig):

  • CONFIG_NFILE_DESCRIPTORS:启用基本伪文件系统和文件描述符支持。
  • CONFIG_FS_ROMFSCONFIG_FS_FATCONFIG_FS_LITTLEFS等:包含特定存储文件系统实现的选项。
  • CONFIG_FS_NXFFSCONFIG_FS_SMARTFS:旧版NuttX文件系统的选项。
  • CONFIG_FS_PROCFSCONFIG_FS_BINFS:其他类型伪文件系统的选项。
  • 与特定文件系统特性相关的选项(例如FAT的长文件名支持、闪存文件系统的磨损均衡)。
  • 启用存储硬件块驱动的选项(例如CONFIG_MMCSD)。

使用menuconfig启用项目所需的文件系统。

结论

  • 由虚拟文件系统(VFS)协调的文件系统是openvela中的关键层,它提供了统一灵活的方式来与硬件设备和物理存储介质上的数据进行交互

  • 通过使用路径表示资源和标准操作集(openreadwrite),VFS简化了应用程序开发,使系统更具模块化。

  • 理解VFS、设备节点、真实文件系统和挂载过程是管理数据和与嵌入式系统中多样化硬件交互的关键。

在下一章,我们将探索网络协议栈,这是另一个常与VFS集成的复杂系统,用于提供网络接口访问,有时甚至以设备类条目形式出现或需要文件类套接字操作。


第五章:网络协议栈

在前面的章节中,我们已经建立了对openvela系统的基础理解。

  • 在第一章:Kconfig中,我们学习了如何选择需要的组件
  • 在第二章:构建系统中,了解了这些组件如何被组装成运行程序
  • 在第三章:驱动程序中,探索了程序如何与独立硬件组件通信
  • 在第四章:文件系统/VFS中,掌握了系统如何以统一方式管理硬件访问(通过/dev等设备节点)和存储数据。

现在,让我们思考如何将嵌入式系统连接到外部世界,特别是通过互联网或本地网络与其他设备通信。
我们的设备如何向服务器发送数据、从手机接收指令,甚至从网站获取信息?这正是网络协议栈的职责所在。

何为网络协议栈?与世界对话

想象您要寄送包裹给远方的人。流程并非简单将物品投入邮筒:

  1. 将物品装入盒子
  2. 包裹填充物并密封
  3. 在外包装清晰标注收件/发件地址
  4. 交付邮局或物流公司
  5. 物流系统添加标签条码,规划运输路线(陆运/空运/海运)
  6. 沿途各节点根据地址信息路由包裹
  7. 最终收件人逐层拆封获取物品

网络通信遵循相似原理!数据需经过精心封装、寻址和路由,才能跨越有线/无线介质,被目标设备和特定应用正确接收解析。

网络协议栈是管理网络数据传输的规则、流程和软件层集合,定义了数据的:

  • 封装与解封装
  • 寻址(网络地址分配)
  • 路由(正确路径选择)
  • 可靠性传输(TCP)或快速传输(UDP)
  • 跨应用解析

它将复杂网络通信任务分解为不同层级处理。

分层架构的优势

分层网络通信架构具备多重优势:

  1. 复杂度管控:每层专注特定功能(如物理连接、跨网络路由、可靠性保障)
  2. 模块化设计:更换某层实现(如以太网转WiFi)无需修改上层
  3. 标准化协议:各层协议(IP/TCP/UDP)行业通用,实现跨厂商设备互通
  4. 抽象隔离:应用无需了解底层传输细节,只需与上层协议交互

协议栈层级

尽管存在OSI七层模型等不同模型,openvela主要采用TCP/IP四层模型:

TCP/IP层级OSI对应层核心功能物流类比
应用层应用/表示/会话层提供应用服务(HTTP网页/FTP文件传输等)实际寄送物品(礼物/文件)
传输层传输层管理跨设备应用通信,添加端口号(类似门牌号),处理可靠性选择物流服务(挂号信/明信片),附加服务选项
网络层网络层跨网络设备通信,添加IP地址(街道/城市/国家)填写收/发件地址,规划全局路线
数据链路层数据链路层直连设备通信,添加MAC地址(建筑编号),准备物理介质本地派送指令,建筑编号标注
物理层物理层物理硬件实现(线缆/连接器/无线电波)道路/卡车/飞机等运输实体

openvela网络栈主要实现传输层、网络层和数据链路层。

应用层接口:套接字(Socket)

openvela应用通过套接字接口与协议栈交互,这是标准POSIX套接字接口,包含以下核心函数:

#include <sys/socket.h>// 创建IPv4 TCP套接字示例
int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP套接字if (sockfd < 0) {perror("socket创建失败");return 1;}struct sockaddr_in serv_addr = {.sin_family = AF_INET,.sin_port = htons(8080),                  // 目标端口8080.sin_addr.s_addr = inet_addr("192.168.1.1") // 目标IP};if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {perror("连接失败");close(sockfd);return 1;}char buffer[1024];ssize_t bytes_recv = recv(sockfd, buffer, sizeof(buffer), 0);  // 接收数据close(sockfd);return 0;
}

套接字描述符(sockfd)类似于文件描述符,通过VFS路由系统调用,但网络通信有独立处理逻辑。

数据传输流程

发送数据流程
在这里插入图片描述

接收数据流程
在这里插入图片描述

网络驱动注册

网络接口通过netdev_ops_s结构注册到协议栈:

struct netdev_ops_s {/* 接口控制 */int (*ifup)(FAR struct netdev_lowerhalf_s *dev);int (*ifdown)(FAR struct netdev_lowerhalf_s *dev);/* 数据收发 */int (*transmit)(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt);int (*receive)(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt);/* 状态获取 */int (*get_macaddr)(FAR struct netdev_lowerhalf_s *dev, FAR uint8_t *mac);
};// 驱动注册示例
int ethernet_driver_register() {struct netdev_lowerhalf_s *dev = kmm_zalloc(sizeof(...));dev->ops = &ethernet_ops;  // 绑定操作集return netdev_lower_register(dev, NET_LL_ETHERNET);
}

Kconfig网络配置

通过Kconfig启用网络功能:

CONFIG_NET=y              # 启用网络协议栈
CONFIG_NET_TCP=y          # 启用TCP协议
CONFIG_NET_IPv4=y         # 启用IPv4
CONFIG_NETDEVICES=y       # 启用网络设备框架
CONFIG_ETH0_DRIVER=y      # 启用以太网驱动
CONFIG_NETUTILS_PING=y    # 包含ping工具

高级功能

openvela支持以下进阶特性:

  • NAT网络地址转换:实现IP共享
  • DHCP客户端/服务端自动IP分配
  • 零拷贝传输:提升吞吐性能
  • 多协议支持TCP/UDP/ICMP/IPv6
  • 虚拟网络设备:TUN/TAP接口
  • 流量整形:QoS服务质量控制

网络测试工具

启用以下工具进行诊断:

ping 192.168.1.1         # ICMP连通性测试
iperf -c 192.168.1.1    # 带宽测试
tcpdump -i eth0         # 抓包分析

总结

网络协议栈是嵌入式系统连接能力的核心。
通过分层模型和标准化接口,实现了从物理信号到应用数据的完整解析。
结合Kconfig配置和驱动开发,可使openvela设备灵活适应各类网络环境。

下一章将深入探讨处理器间通信(IPC),解析多核/分布式系统中的协同工作机制~

http://www.xdnf.cn/news/13694.html

相关文章:

  • 【React】SWR 和 React Query(TanStack Query)
  • 力扣HOT100之技巧:169. 多数元素
  • 【Zephyr 系列 21】OTA 升级与产测系统集成:远程配置、版本验证、自动回滚机制设计
  • 请问黑盒测试和白盒测试有哪些方法?
  • 力扣-198.打家劫舍
  • leetcode HOT100(49.字母异位词分组)
  • 怎样解决在ubuntu 22.04上QT: DataVisualization控件显示黑屏的问题
  • 触觉智能RK3576核心板工业应用之软硬件全国产化,成功适配开源鸿蒙OpenHarmony5.0
  • LangGraph--带记忆和工具的聊天机器人
  • Modbus TCP转DeviceNet网关连接ABB变频器配置案例
  • 破解关键领域软件测试“三重难题”:安全、复杂性、保密性
  • 电脑、手机长时间不关机可以吗
  • Rabbitmq后台无法登录问题解决
  • Genio 1200 Evaluation MT8395平台安装ubuntu
  • 全栈监控系统架构
  • 22、话题重名及解决方案
  • 销售预测的方法与模型(二)丨商品与库存分类——基于数据模型运营的本质和底层逻辑销售
  • Spring MVC 入门案例:从代码到原理的深度剖析
  • Docker 构建文件代码说明文档
  • qemu-kvm+virt-manager创建虚拟机设置桥接模式
  • 告别手动做PPT!4款AI工具实现自动化生成
  • Python—turtle绘图库使用方法
  • 【论文阅读笔记】高光反射实时渲染新突破:3D Gaussian Splatting with Deferred Reflection 技术解析
  • 技术专栏|LLaMA家族——模型架构
  • 算法学习笔记:2.大根堆算法——数据流的中位数​​or最后一块石头的重量
  • 【Redisson】锁可重入原理
  • Redis初识第一期
  • 从0到1构建高并发秒杀系统:实战 RocketMQ 异步削峰与Redis预减库存
  • 接口测试常用工具及测试方法(基础篇)
  • 【MySQL】视图