F´ 无操作系统裸机部署实战被动组件架构、Baremetal OS 与线程虚拟化【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprimeF´F Prime飞行软件框架并不强制依赖操作系统通过裸机Baremetal模式开发者可以让 F´ 应用在无 OS 的处理器上运行用单个main()主循环配合硬件定时器驱动整套被动组件体系。本文基于 docs/user-manual/framework/run-baremetal.md 展开结合仓库中的RateGroupDriver、PassiveRateGroup源码与 CMake 配置完整讲解裸机环境的特征、被动组件架构、Baremetal OS与MicroFs支持特性、配置调优手段以及面向 Active 组件的线程虚拟化protothreading原理帮助读者在资源受限的 MCU如 Arduino、STM32 平台上落地 F´ 应用。什么是 BaremetalBaremetal裸机指不运行操作系统的处理器/应用环境。在裸机环境中没有软件来提供进程或线程不存在任务调度器也不存在线程上下文切换资源通常受限RAM、存储空间都很有限需要精打细算只有一个入口一个单独的main()函数通常运行一个连续控制循环例如while(true) { ... }所有执行都从这一处流出可以使用中断服务程序ISR硬件中断可以在没有 OS 的情况下被注册和处理。从源码结构看F´ 框架本身就包含了对OS 抽象层OSAL的完整建模见 Os 目录下的Task、Queue、Mutex、CountingSemaphore等抽象接口裸机模式正是 OSAL 的一类特殊实现。理解了这一点就能明白为什么 F´ 核心组件可以在裸机上复用。F´ 与 BaremetalF´ 不要求必须运行在操作系统之上。应用可以完全由一组passive被动组件构成由一个Timer 组件通过被动速率组Passive Rate Group驱动整个应用。但是框架中大量标准的 F´active主动组件依赖Task抽象——该抽象由 OSAL 提供——因此裸机部署需要一套适配方案。裸机模式正是为此设计让 F´ 核心组件能在无 OS 部署中工作同时适应环境的资源约束并提供如何在为完整 OSAL 适配编写的组件之上支持裸机的指导。在典型的裸机 F´ 系统中一个硬件定时器以固定间隔驱动Svc.RateGroupDriver组件速率组驱动再分频驱动各速率组Rate Group及其挂载组件没有并发调度——系统表现为一个顺序的工作队列在每个定时器 tick 上被依次派发。被动组件的重要性首先且最重要的是裸机 F´ 系统应尽可能避免使用 Active 组件。原因是 Active 组件拥有自己的线程需要 OS 级线程支持才能并发执行——这在裸机上不存在。因此应使用由速率组驱动、或在主线程上被循环调用的passive被动或queued排队组件。关于active与passive/queued组件的取舍以及如何在两者之间迁移的设计模式可参阅 Selecting Component, Port, and Command Kinds。[!NOTE] 如果必须使用 Active 组件应首先考虑改用 RTOS 而不是裸机。线程虚拟化见下文 线程虚拟化只是开发者在迁移到完整 RTOS、或将 Active 组件替换为queued等价物过程中的过渡台阶。如果系统能完全由passive和queued组件定义那么每个端口调用最终都会在同步调用中执行执行上下文完全下放给每个组件线程调度器的需求就消失了。这种委托执行上下文的来源问题将在下文讨论。架构裸机 F´ 应用中组件围绕被动速率组模式组织定时器驱动以固定速率调用被动速率组速率组再驱动所有已连接组件的执行。由于没有线程和队列所有组件都使用同步端口被调用时同步执行。[!TIP] 在裸机系统中queued组件的消息通常通过速率组派发且每次调用限制有界的最大消息派发次数以保证主循环的每次扫描时间可控。仓库中的源码印证了这一架构Svc.RateGroupDriver在 Svc/RateGroupDriver/RateGroupDriver.fpp 中被声明为passive component RateGroupDriverSvc.PassiveRateGroup同样在 Svc/PassiveRateGroup/PassiveRateGroup.fpp 中被声明为passive component PassiveRateGroup。两个关键组件均为被动组件意味着它们本身不拥有线程所有工作都在**调用者即定时器驱动的执行上下文**中完成。Baremetal 支持包的功能F´ 通过 fprime-community 下的fprime-baremetal支持包为裸机部署提供支持该包以 F´ 库的形式发布提供被动组件实现及下述特性。Baremetal OSOs/Baremetal模块提供了 OS 抽象层OSAL的一种实现用于模拟线程、消息队列及其他 OS 特性。这使得依赖 OS 抽象的 F´ 核心组件可以在没有完整操作系统的情况下使用。关键特征模拟 OS 特性如线程与 F´ OSAL 模型保持兼容。[!CAUTION] 需要线程模拟的用户应先阅读下文 线程虚拟化那里介绍了一种使用 protothreading 的实验性方法。Baremetal 与 RTOS 的取舍维度BaremetalRTOS时序控制开发者完全掌控无 OS 开销由调度器引入一定抖动与开销适用硬件资源极度受限的处理器小 RAM 的 MCU复杂时序要求或多独立任务的系统调度能力无抢占、无优先级健壮的调度、任务管理与同步选择条件系统可由速率组驱动的被动组件表达且代码体积/内存占用是硬指标需要抢占式多任务、优先级反转处理或大量 Active 组件选择建议当你的系统可以表达为一组由速率组驱动的被动组件、且最小化代码体积和内存占用是关键时选裸机当需要抢占式多任务、优先级反转处理或使用大量 Active 组件时选 RTOS。MicroFsMicroFs为需要文件访问的组件提供内存中的基础文件系统。这对依赖文件系统接口的组件尤其有用例如管理数据产品Data Products、参数存储或序列文件加载的组件。提供基础文件系统操作open、read、write、close文件存放在 RAM 中仅在处理器上电期间持久——断电即失。对于带 flash 存储或其他非易失存储的系统开发者可以选择提供自己的文件系统实现或封装第三方库如 LittleFS、FatFs。做法是实现Os::FileSystem接口使 F´ 组件能透明地与自定义文件系统交互——这正是 F´ OSAL 的设计意图文件系统也是可替换的抽象层对应 Os/FileSystem.hpp 等 OSAL 文件接口。配置与调优F´ 提供大量配置选项来适配裸机部署的受限环境用于最小化代码体积、内存占用和其他资源消耗。F´ 配置F´ 有大量配置选项可以把 F´ 的规模缩小以适应资源受限环境。这些配置位于项目自身的default/config目录拷贝中仓库根目录对应 default/config。完整配置细节参见 User Guide: Configuring F´。示例配置选项包括开关功能按需打开/关闭特性指定缓冲区和存储大小调整最大字符串长度、队列深度、对象名长度禁用部署中不需要的组件和服务。完整选项列表见 User Guide: Configuring F´。端口调用优化Port Call OptimizationF´ 为端口连接提供替代代码生成方式用于消除部分抽象层从而在资源受限环境中降低开销和代码体积。这是一个高级特性可通过编译选项FW_DIRECT_PORT_CALLS启用。按文档说明该功能涉及构建缓存中的自动生成代码autocoded code有兴趣的项目可以自行调研另外该选项未来可能默认启用。对裸机部署而言减少端口调用链的间接层数意味着更小的二进制与更快的同步调用路径。裸机调度器 CMake 选项与线程虚拟化直接相关的 CMake 选项是FPRIME_USE_BAREMETAL_SCHEDULER其定义在 cmake/options.cmake 中。从源码可以看到它的取值约束# e.g. -DFPRIME_USE_BAREMETAL_SCHEDULERON if (DEFINED FPRIME_USE_BAREMETAL_SCHEDULER AND NOT ${FPRIME_USE_BAREMETAL_SCHEDULER} STREQUAL ON AND NOT ${FPRIME_USE_BAREMETAL_SCHEDULER} STREQUAL OFF) message(FATAL_ERROR FPRIME_USE_BAREMETAL_SCHEDULER must be set to ON, OFF, or not supplied at all) endif()ON使用裸机单上下文调度替代 OS 调度器循环遍历各 Active 组件、每次调用一次派发OFF使用默认系统线程库调度未指定由平台文件设定默认值——在 cmake/platform/platform.cmake.template 中可见平台模板默认将其置为OFF并FIND_PACKAGE(Threads REQUIRED)查找线程库而当使用裸机调度器时则不再要求线程库。该选项不仅面向无 OS 系统也可以用于把执行限制在单线程或在 PC 上测试裸机调度器。实现建议实施裸机 F´ 应用时应遵循以下三条核心建议。组件组织按照 Rate Group 模式 开发组件由硬件定时器以所需速率驱动使用PassiveRateGroup驱动一组组件包括 F´ 核心组件所有组件应使用被动端口原因如前文所述。结合源码深入理解Svc.RateGroupDriver的CycleIn_handler在 Svc/RateGroupDriver/RateGroupDriver.cpp 中实现分频逻辑——每个CycleOut输出端口对应一个Divider条目divisor除数与offset偏移当(m_ticks % divisor) offset时触发该端口的CycleOut_out调用m_rollover为所有非零除数之积用于防止整型回绕跳过周期。例如除数为 4 的端口在每次CycleIn调用的每第 4 次被触发。这正是裸机系统中一个时钟源派生多种速率的底层实现。缩放 F´ 特性通过以下方式最小化资源占用关闭不需要的端口序列化如未用到的串行化传输将命令表和遥测存储缩放到最小所需尺寸调整缓冲区大小以匹配实际需求禁用未使用的特性。选择执行上下文确保某个调用能触发组成 F´ 系统的所有组件是裸机系统运行的关键否则部分组件将永远不会运行。通常做法是把裸机 F´ 系统组合成全部由速率组驱动的组件集合。此外用户也可以从主线程直接调用组件。这样设计系统可以保证所有执行都源自一个源头速率组驱动。问题因此被简化为以设定速率向速率组驱动提供一个执行上下文——所有需要的调用都会在速率组及其派生速率的扫描过程中执行。[!NOTE] 也存在其他方案见下文 线程虚拟化但这些方法仍然需要一个上下文来运行。虽然如何为速率组驱动提供执行上下文的完整讨论超出了本文范围但嵌入式软件通常长这样——下面execute()的无限循环动作应在设定间隔触发速率组驱动// Run once setup setup(); // Do this forever while (true) { execute(); // Cycle rate groups, and or thread virtualization here. }现在只需要确定这个间隔何时到达。可以通过以下方式之一在硬件时钟信号上自旋通过读取时钟寄存器计算经过时间使用定时库函数使用sleep()调用由**定时器驱动的中断服务程序ISR**触发。[!NOTE] ISR 是复杂事物在采用该路线前应深入研究。特别是ISR 不应直接执行速率组而应设置标志位或投递启动消息让主循环中的while (true) {}自旋检测到该信号后再启动速率组。F´ 的 ISR Device Driver Pattern 文档对此有更详细的约束讨论ISR 中不得阻塞、不得加锁、不得断言工作应尽量延迟到线程上下文。线程虚拟化[!NOTE] 就 F´ 而言这是一项专门技术。在将其用于生产/飞行环境之前应充分理解其实现。为什么需要线程虚拟化有些系统——甚至裸机系统——需要Active 组件。许多Svc组件在设计上就是 Active 组件。要求所有项目从诞生之初就抛弃框架提供的所有 Active 组件是不切实际的。因此 F´ 增加了虚拟化线程的能力使项目在迁移到全被动组件系统的过程中仍能在开发阶段使用这些组件。标准裸机模式只使用被动组件线程虚拟化是需要在 Active 组件时的进阶替代方案。启用方式CMake 选项FPRIME_USE_BAREMETAL_SCHEDULER见 cmake/options.cmake。下文介绍线程虚拟化系统的内部工作机制。自定义任务的定义要求使用线程虚拟化技术时对自定义任务/线程需要格外小心。该设计依赖只运行一次、并把循环部分外部化的线程。因此自定义任务必须包装满足以下实现要求的函数函数不得循环函数绝不能阻塞执行函数应执行线程的一个切片然后返回——即完成一小块有界的工作单元后通过返回让出控制权。这是一种协作式调度每个函数完成一小块工作并自愿通过返回让出控制权允许下一个函数运行。与抢占式线程OS 可随时中断线程不同协作式调度依赖每个任务行为良好。如果任何函数无限循环或阻塞其他任务将永远没有机会执行导致系统锁死或行为异常。[!TIP] F´ 的 Active 组件实现已满足上述要求。用户只需对自定义线程遵循这些期望即可。工作原理Active 组件的核心是一条线程。线程通常需要 OS 提供调度器为其分配执行上下文线程因此可以表现得像完全拥有自己的执行上下文而 OS 在幕后将其掩盖。线程虚拟化的目的是展开这些线程使它们共享单一执行上下文线程的并发行为被虚拟化。这种技术称为protothreading原线程。[!NOTE] 必须区分真正的并行多指令在多核上同时执行与并发多任务在单核上通过交错执行取得进展。在单核裸机系统上真正的并行不可能实现。线程虚拟化提供的是并发——每个组件轮流执行制造出并行运行的表象。每个支撑 Active 组件的 F´ 线程可大致建模为下面的代码线程循环直到系统关闭每次循环迭代中阻塞暂停执行直到消息到达然后派发消息并回到阻塞状态等待下一条消息Component1 :: run_thread() { while (!shutdown) { msg block_get_message(); dispatch(msg); } }这里block_get_message();获取消息并阻塞直到消息到达。这个循环很容易用较低效的模型实现不断迭代循环、检查消息是否到达、仅在消息可用时才派发。如下所示阻塞等待被替换为不断循环的忙等待void Component1 :: run_once() { if (message_count() 0) { msg nonblock_get_message(); dispatch(msg); } return; } Component1 :: run_thread() { while (!shutdown) { comp1.run_once(); } }这里为了清晰起见把迭代提取到了run_once函数中。第一个函数中的阻塞等待被替换为对 if 条件的自旋直到消息可用然后执行派发。只需稍加外推就能把所有组件的run_once函数移入单个循环、依次调用while (!shutdown) { comp1.run_once(); comp2.run_once(); comp3.run_once(); ... }如上所见run_once不阻塞因此每个组件在让出给下一个组件之前都能获得一个执行切片。并发被虚拟化了——每个组件轮流运行、共享处理器无需编写完整线程调度器也不要求处理器指令集支持线程上下文切换。在 F´ 内部为 Active 组件任务实现了一个并行实现返回而不是在接收消息时阻塞。当 F´ 配置中启用BAREMETAL_SCHEDULER时使用该替代实现。Os/Baremetal下存在一个顺序调度器的实现该调度器监听任务注册并在类似下面的主程序循环中调用所有线程执行setup(); // Setup F´ while (true) { scheduler.run_once(); }这样即使系统中存在 Active 组件它们的线程也在单一主循环上下文中以协作式轮转调度方式推进与裸机系统单一入口、顺序执行的模型保持一致。结论裸机模式使 F´ 能够在没有操作系统的资源受限处理器上运行同时仍可复用 F´ 核心组件。通过使用Baremetal OS 抽象层、用于文件操作的MicroFs以及精心的配置调优开发者可以在 Arduino、STM32 等微控制器平台上部署 F´ 应用。实现时把握三条主线即可全部组件由速率组驱动的被动组件组织、按需缩放 F´ 特性配置、缓冲区、命令表、遥测、以单一主循环为所有组件提供执行上下文。若必须使用 Active 组件则通过FPRIME_USE_BAREMETAL_SCHEDULER启用线程虚拟化把每条线程改写成不循环、不阻塞、单切片返回的run_once形式由Os/Baremetal的顺序调度器在主循环中轮转调度——让裸机系统在保持 F´ 组件架构的同时获得协作式并发。延伸阅读F´ on Multi-Core Systems多核 F´ 部署指南Rate Group Pattern裸机系统中驱动组件执行的速率组设计模式含RateGroupDriver分频算法与系统时钟源讨论ISR Device Driver Pattern裸机/RTOS 环境下的 ISR 约束与驱动模式Selecting Component, Port, and Command Kindsactive/passive/queued 组件取舍与迁移模式User Guide: Configuring F´完整的配置选项列表。【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考