在 F´ 中从零实现 OS 抽象层(OSAL):以 MyOs 平台与 Os::Mutex 为例的完整移植指南
发布时间:2026/9/15 17:40:58 作者:尧图编辑部 阅读量:1,286
:以 MyOs 平台与 Os::Mutex 为例的完整移植指南)
在 F´ 中从零实现 OS 抽象层OSAL以 MyOs 平台与 Os::Mutex 为例的完整移植指南【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime本篇技术指南面向需要将 F´F Prime飞行软件与嵌入式系统框架移植到新操作系统或新硬件平台的开发者讲解如何实现 F´ 的 OS 抽象层OS Abstraction LayerOSAL。F´ 的 OSAL 为操作系统服务互斥锁、任务、文件、队列、时钟等提供统一接口使 F´ 应用源码无需修改即可运行于多种操作系统之上。读完本文你将掌握 OSAL 的委托delegate三层架构、Os::Mutex服务的完整实现流程包含句柄定义、错误码映射、委托工厂注册与 CMake 接入以及链接期选择与编译期选择两种实现机制的性能取舍并能够将同一流程复制到其他全部 OSAL 服务。前置知识开始实现前建议先具备以下基础理解 OSAL 架构与设计文档尤其是其中的 委托模式Delegate Pattern 与 实现架构一套可正常工作的 F´ 构建环境用于测试熟悉 F´ 库libraries开发 及其中 可选的平台文件夹与平台文件了解目标 OS 的本地 API如互斥锁、任务创建、文件 I/O。OSAL 总体架构三层委托模式OSAL 通过委托模式将 F´ 应用代码与平台相关的系统调用解耦。每个 OSAL服务Mutex、File、Task 等均由三层构成见 OSAL SDD §5.1层示例说明接口InterfaceOs::MutexInterface定义服务契约的纯虚基类见 Os/Mutex.hpp包装类WrapperOs::Mutex应用代码直接使用的final具体类内部持有一个接口委托的引用见 Os/Mutex.hpp实现ImplementationOs::Posix::Mutex::PosixMutex实现接口的具体类这是移植新 OS 时唯一需要编写的部分见 Os/Posix/Mutex.hpp工作方式构建系统在链接期选择链接哪个Default*.cpp文件该文件提供getDelegate()工厂函数包装类构造时调用Interface::getDelegate()通过 placement new 在定长字节数组中就地构造具体实现。此后应用代码的所有调用都经由包装类转发给其m_delegate对接口实现的引用运行时通过虚函数表完成分发见 Os/Mutex.hpp。本文以虚构操作系统MyOs的Os::Mutex服务为例走通全部流程其余 OSAL 服务步骤完全一致。Step 1 — 搭建库Library结构OSAL 实现被打包为 F´ 库。创建如下目录结构fprime-my-os/ ├── FprimeMyOs/ │ └── Os/ │ ├── CMakeLists.txt │ ├── Mutex.hpp │ ├── Mutex.cpp │ └── DefaultMutex.cpp └── library.cmake根目录的library.cmake负责把 OSAL 模块目录加入构建# library.cmake add_fprime_subdirectory(${CMAKE_CURRENT_LIST_DIR}/FprimeMyOs/Os)[!IMPORTANT] 所有库的模块目录都必须以FprimeMyOs/作为命名空间前缀以避免与 F´ 核心模块及其他库发生命名冲突。Step 2 — 实现一个 OSAL 模块每个 OSAL 服务对应一个 CMake/C 模块模块需要提供继承自对应接口的实现类。fprime/Os/下的接口头文件如Os/Mutex.hpp、Os/File.hpp、Os/Task.hpp定义了必须实现的纯虚方法契约。为了通过操作系统原生结构维护状态例如 POSIX 的pthread_mutex_t句柄实现类内含一个m_handle成员封装目标 OS 的原生原语。2.1 — 定义句柄与实现类创建FprimeMyOs/Os/Mutex.hpp// FprimeMyOs/Os/Mutex.hpp #ifndef MYOS_MUTEX_HPP #define MYOS_MUTEX_HPP #include Os/Mutex.hpp #include myos/mutex.h // MyOs native mutex API namespace Os { namespace MyOs { namespace Mutex { // Handle wraps the native OS mutex primitive struct MyOsMutexHandle : public MutexHandle { myos_mutex_t m_mutex; // MyOs native mutex type }; // Implementation of MutexInterface using MyOs APIs class MyOsMutex : public MutexInterface { public: MyOsMutex(); ~MyOsMutex() override; MutexHandle* getHandle() override; Status take() override; Status release() override; private: MyOsMutexHandle m_handle; // Handle wrapping the native OS mutex }; } // namespace Mutex } // namespace MyOs } // namespace Os #endif[!TIP] 逐个查看Os/下每个接口头文件如 Os/Mutex.hpp、Os/File.hpp、Os/Task.hpp确认需要实现的纯虚方法集合。每个接口还定义了各自的Status枚举——你的实现必须返回恰当的状态值。例如MutexInterface::Status在 Os/Mutex.hpp 中定义为OP_OK、ERROR_BUSY、ERROR_DEADLOCK、NOT_SUPPORTED、ERROR_OTHER。2.2 — 实现方法创建FprimeMyOs/Os/Mutex.cpp// FprimeMyOs/Os/Mutex.cpp #include FprimeMyOs/Os/Mutex.hpp #include Fw/Types/Assert.hpp namespace Os { namespace MyOs { namespace Mutex { MyOsMutex::MyOsMutex() : Os::MutexInterface(), m_handle() { int status myos_mutex_init(this-m_handle.m_mutex); FW_ASSERT(status 0, static_castFwAssertArgType(status)); } MyOsMutex::~MyOsMutex() { myos_mutex_destroy(this-m_handle.m_mutex); } MyOsMutex::Status MyOsMutex::take() { int status myos_mutex_lock(this-m_handle.m_mutex); switch (status) { case 0: return Status::OP_OK; case MYOS_EBUSY: return Status::ERROR_BUSY; case MYOS_EDEADLK: return Status::ERROR_DEADLOCK; default: return Status::ERROR_OTHER; } } MyOsMutex::Status MyOsMutex::release() { int status myos_mutex_unlock(this-m_handle.m_mutex); // TODO: Map MyOs error codes to appropriate Status values return (status 0) ? Status::OP_OK : Status::ERROR_OTHER; } MutexHandle* MyOsMutex::getHandle() { return this-m_handle; } } // namespace Mutex } // namespace MyOs } // namespace Os源码级参考仓库中 Os/Posix/Mutex.cpp 是同一模式的规范参考——其构造函数用pthread_mutexattr_settype(attribute, PTHREAD_MUTEX_ERRORCHECK)与PTHREAD_PRIO_INHERIT配置属性后调用pthread_mutex_inittake()/release()分别调用pthread_mutex_lock/pthread_mutex_unlock并统一交给Os::Posix::posix_status_to_mutex_status()转换状态。注意 POSIX 实现同样在构造失败时使用FW_ASSERT断言见 Os/Posix/Mutex.cpp这与上例中 MyOs 的用法一致。[!CAUTION] 当创建Os::Mutex包装对象时底层实现对象MyOsMutex是通过 placement new 就地构造在定长字节数组FW_MUTEX_HANDLE_MAX_SIZE之内的。该常量必须在库配置的PlatformCfg.fpp中定义。编译时编译器会依据sizeof(MyOsMutex)帮你判断所需尺寸。仓库默认值见 default/config/PlatformCfg.fppconstant FW_MUTEX_HANDLE_MAX_SIZE 72同时constant FW_HANDLE_ALIGNMENT 8定义了句柄的对齐要求。Step 2.5 — 可选编译期选择覆盖性能优化默认情况下 F´ 使用链接期选择通过委托模式在运行时进行虚函数分发见 OSAL SDD §5.2。对于性能敏感平台例如时序约束严格的裸机嵌入式系统项目可以覆盖默认配置启用编译期选择消除虚函数分发开销并允许编译器进行函数内联和链接期优化LTO。[!NOTE] F´ OSAL 提供了默认配置头如 default/config/OsDelegateRawTime.hpp其内容为namespace Os { class DelegateRawTime; using RawTime DelegateRawTime; }并#define OS_RAW_TIME_HEADER Os/DelegateRawTime.hpp即启用别名并默认走链接期选择。这些属于 F´ 核心配置的一部分。本步介绍项目如何针对自身部署覆盖这些默认值。为什么需要编译期选择详细取舍见 OSAL SDD §5.2.2 Compile-Time Selection。概括如下链接期选择默认使用虚函数分发约 10~50 个时钟周期开销但提供运行时灵活性可在运行时替换实现不同构建产物 ABI 兼容编译期选择允许内联与 LTO消除性能关键代码的调用开销可将简单操作如读时钟从数十个周期降到个位数指令代价是失去运行时灵活性、不同覆盖配置的构建产物 ABI 不兼容。该优化对裸机系统中遥测时间戳这类高频操作最有价值。项目如何覆盖为编译期选择项目通过提供自己的配置头替换 F´ 默认配置来实现编译期选择。以RawTime为例创建项目专用的config/OsDelegateRawTime.hppStep 1:在项目 config 目录创建config/OsDelegateRawTime.hpp// my-project/config/OsDelegateRawTime.hpp #ifndef CONFIG_OS_DELEGATERAWTIME_HPP #define CONFIG_OS_DELEGATERAWTIME_HPP // Guard against circular dependencies (same as F´ default) #if defined(OS_RAWTIME_HPP_) || defined(OS_RAWTIMEINTERFACE_HPP_) || defined(OS_DELEGATERAWTIME_HPP_) #error config/OsDelegateRawTime.hpp must not include Os OSAL headers - circular dependency detected #endif // Forward-declare your concrete implementation class namespace MyPlatform { class MyRawTime; } namespace Os { // Alias Os::RawTime directly to your implementation (no delegate wrapper) using RawTime MyPlatform::MyRawTime; } // Point to your implementation header (will be included after RawTimeInterface.hpp) #define OS_RAW_TIME_HEADER MyPlatform/Os/RawTime.hpp #endif // CONFIG_OS_DELEGATERAWTIME_HPPStep 2:在项目config/CMakeLists.txt中注册该配置头register_fprime_config( # ... project config name other options CONFIGURATION_OVERRIDES ${CMAKE_CURRENT_LIST_DIR}/OsDelegateRawTime.hpp # ... other project override config headers )Step 3:确保实现类继承自RawTimeInterface// MyPlatform/Os/RawTime.hpp #include Os/RawTimeInterface.hpp namespace MyPlatform { class MyRawTime : public Os::RawTimeInterface { // Implementation of RawTimeInterface methods // Now called directly without delegation wrapper // ... }; } // namespace MyPlatforminclude 顺序是承重结构Os/RawTime.hpp依次包含Os/RawTimeInterface.hpp它先包含 config 头以获得Os::RawTime别名再通过OS_RAW_TIME_HEADER宏包含具体实现头。config 头绝不能包含任何 Os OSAL 头文件否则会形成循环依赖该约束在 Os/RawTime.hpp 顶部有完整注释说明且Os/RawTime.hpp会在OS_RAW_TIME_HEADER未定义时报错#error OS_RAW_TIME_HEADER must be defined in config/OsDelegateRawTime.hpp。关键约束完整约束清单见 OSAL SDD §5.2.2要点如下ABI 兼容性构建目录中的所有目标文件必须使用同一配置禁止循环依赖配置头不得包含 Os OSAL 头文件必须继承接口实现必须继承自对应接口类例如RawTimeInterface跳过委托工厂编译期选择不需要Default*.cpp。哪些服务支持编译期选择目前 F´ 仅RawTime支持别名化aliasing的编译期选择见 OSAL SDD §5.2.2。RawTime之所以特殊是因为getDelegate还接收一个RawTimeSource source参数——默认枚举定义在 default/config/Os/RawTimeSource.hpp在 Linux/Darwin 上每个枚举值直接携带clockid_tRAWTIME_DEFAULT CLOCK_REALTIME、RAWTIME_MONOTONIC CLOCK_MONOTONIC、Linux 下还有RAWTIME_BOOTTIME项目可通过覆盖该头更改默认时钟例如RAWTIME_DEFAULT CLOCK_MONOTONIC。Step 3 — 注册委托工厂Delegate Factory[!NOTE] 若你在 Step 2.5 启用了编译期选择可跳过创建DefaultMutex.cpp或DefaultRawTime.cpp等因为委托工厂不再被使用直接跳到 Step 4。创建FprimeMyOs/Os/DefaultMutex.cpp。该文件提供getDelegate()工厂函数Os::Mutex包装类构造时调用它以构建实现// FprimeMyOs/Os/DefaultMutex.cpp #include Os/Delegate.hpp #include FprimeMyOs/Os/Mutex.hpp namespace Os { MutexInterface* MutexInterface::getDelegate(MutexHandleStorage aligned_new_memory) { return Os::Delegate::makeDelegateMutexInterface, Os::MyOs::Mutex::MyOsMutex( aligned_new_memory); } } // namespace OsOs::Delegate::makeDelegate是 Os/Delegate.hpp 中提供的辅助函数它执行 placement new 并通过static_assert在编译期完成三项校验MyOsMutex派生自MutexInterfacestd::is_base_ofsizeof(MyOsMutex)不超出MutexHandleStorage即FW_MUTEX_HANDLE_MAX_SIZEalignof(MyOsMutex)与FW_HANDLE_ALIGNMENT对齐兼容(FW_HANDLE_ALIGNMENT % alignof(Implementation)) 0。placement new 完成后还会用FW_ASSERT确保返回指针非空。Os/Delegate.hpp提供三个重载makeDelegate(aligned_new_memory)普通构造无拷贝构造支持Mutex、Task 等makeDelegate(aligned_new_memory, to_copy)支持拷贝构造如FileInterface的可选const FileInterface* to_copy参数见 Os/Delegate.hppmakeDelegate(aligned_new_memory, to_copy, argument)额外支持构造参数如RawTimeInterface的RawTimeSource source。[!NOTE] 部分接口的getDelegate签名带有额外参数FileInterface带有可选的const FileInterface* to_copy用于拷贝构造RawTimeInterface带有可选的const RawTimeInterface* to_copy与RawTimeSource source同时支持拷贝构造与可选择的时钟源见 Os/RawTimeInterface.hpp。应将source通过三参数makeDelegate(aligned_new_memory, to_copy, source)重载传给实现构造函数实现应把所选时钟源存进句柄以便拷贝时保留在now()中按它读取时钟并且当两个实例时钟源不同时从getTimeInterval()返回INVALID_PARAMS。参考实现见 Os/Posix/RawTime.cppPosixRawTime构造函数将source转为clockid_t存入m_clock_idgetTimeInterval()中当双方m_clock_id不同时直接返回Status::INVALID_PARAMS。makeDelegate辅助函数支持以上全部签名。请逐一核对各接口头文件中getDelegate的确切签名。Step 4 — 在 CMake 中注册模块创建FprimeMyOs/Os/CMakeLists.txt使用register_os_implementation函数注册实现# FprimeMyOs/Os/CMakeLists.txt restrict_platforms(MyOs) register_os_implementation(Mutex MyOs) # args: ServiceName PlatformSuffix这一次register_os_implementation调用会完成三件事期望当前目录下存在Mutex.hpp、Mutex.cpp与DefaultMutex.cpp三个文件创建Os_Mutex_MyOs_Implementation目标包含实现源码Mutex.cpp、Mutex.hpp创建Os_Mutex_MyOs目标包含DefaultMutex.cpp委托工厂并注册为Os_Mutex的实现。若模块依赖其他模块作为附加参数传入register_os_implementation(Task MyOs MyOs_Shared Fw_Time)若多个接口共享同一个Default*.cpp将它们并列列出register_os_implementation(Mutex;ConditionVariable MyOs MyOs_Shared) register_os_implementation(File;FileSystem;Directory MyOs MyOs_Shared)restrict_platforms(MyOs)确保这些实现仅在选中MyOs平台时构建。源码级对照该函数实现在 cmake/API.cmakeregister_os_implementation内部调用add_fprime_supplied_os_module其调用约定NAMES为分号分隔的 1 个或多个服务名首项作为模块名如File;Directory;FileSystem组成名为File的模块SUFFIX为实现后缀如PosixARGN为额外模块依赖。仓库中 Os/Posix/CMakeLists.txt 是最完整的真实用法示例register_os_implementation(File;FileSystem;Directory Posix Os_Posix_Shared) register_os_implementation(Console Posix) register_os_implementation(Task Posix Os_Posix_Shared Fw_Time) register_os_implementation(Mutex;ConditionVariable Posix Os_Posix_Shared) if(NOT APPLE) register_os_implementation(CountingSemaphore Posix Os_Posix_Shared) endif() register_os_implementation(RawTime Posix Os_Posix_Shared)其中Os_Posix_Shared是 POSIX 后端共享的错误码转换模块Os/Posix/error.cppPOSIX 信号量因 macOS 上sem_init返回ENOSYS而被条件排除交由 Darwin 实现覆盖。Step 5 — 单元测试深入的单元测试编写超出本 How-To 范围但对保证实现正确性至关重要。F´ 为多数 OSAL 服务提供了与操作系统无关的单元测试位于 Os/test/ut按服务分子目录mutex/、file/、task/、rawtime/、condition/、countingsemaphore/、queue/、directory/、filesystem/、cpu/、memory/等。这些测试可以针对你的实现运行以验证其行为符合接口契约。也可以按需添加额外测试。以 Mutex 为例Os/test/ut/mutex/CommonTests.cpp 通过 STest 规则引擎覆盖锁定/解锁、take/release 及随机化顺序测试POSIX 后端通过register_fprime_ut的CHOOSES_IMPLEMENTATIONS Os_Mutex_Posix将通用测试挂接到自己的实现上见 Os/Posix/CMakeLists.txt。你的 MyOs 后端可照此模式注册Os_Mutex_MyOs运行同一套通用测试。Step 6 — 为所有需要的 OSAL 服务重复该流程上述Os::Mutex流程适用于每个 OSAL 模块。对每个模块创建Module.hpp包含句柄结构体与继承自接口的实现类创建Module.cpp使用目标 OS 原生 API 实现各虚方法创建DefaultModule.cpp实现getDelegate()工厂在CMakeLists.txt中用register_os_implementation注册。[!NOTE] F´ 提供两个平台无关的队列实现Os_Generic_PriorityQueue基于互斥锁多数平台的默认选择与Os_Generic_LocklessPriorityQueue无锁、ISR 安全——适用于需要在中断上下文收发消息的目标。你无需编写 OS 特定队列除非这两种通用实现都不适合你的目标。参考 Os/Generic/DefaultPriorityQueue.cpp头文件注释声明其为Os/Posix/DefaultFile.cpp的产物实际内容是把QueueInterface::getDelegate指向Os::Generic::PriorityQueue。可实现的完整 OSAL 模块集合见 OSAL SDD §2 Core Services模块接口关键方法MutexMutexInterfacetake(),release()CountingSemaphoreCountingSemaphoreInterfacewait(),waitTimeout(),tryWait(),post()ConditionVariableConditionVariableInterfacepend(),notify(),notifyAll()TaskTaskInterfacestart(),join(),delay()QueueQueueInterfacesend(),receive()FileFileInterfaceopen(),read(),write(),close(),seek()FileSystemFileSystemInterfacerename(),remove(),stat()DirectoryDirectoryInterfaceopen(),read(),close()ConsoleConsoleInterfacewrite()RawTimeRawTimeInterfacenow(),getTimeInterval()getDelegate接收RawTimeSourceCpuCpuInterfacegetCount(),getTicks()MemoryMemoryInterfacegetUsage()此外还有基于核心服务构建的派生服务如ConditionVariable依赖Mutex、ScopeLock为 Mutex 的 RAII 助手、IntervalTimer依赖RawTime以及Os::Generic命名空间下的通用服务详见 OSAL SDD §3-4。每个接口的完整方法签名与状态枚举以Os/下对应接口头文件为准。Step 7 — 在平台上使用实现更多指引见 移植指南Porting Guide——该文档将新平台移植分解为 CMake 工具链与平台文件、Fw::Types与配置、硬件驱动、OSAL 支持四部分完整的 F´ 平台移植 How-To 文档也将陆续推出。要点要使用新的 OSAL 实现需要在平台 CMake 文件中将Os_Module_Suffix目标加入CHOOSES_IMPLEMENTATIONS列表。这与 POSIX 后端测试的做法一致如CHOOSES_IMPLEMENTATIONS Os_Mutex_Posix见 Os/Posix/CMakeLists.txt。社区中已有 Zephyr、VxWorks、FreeRTOS 等平台支持包的实现可作参考详见下文资源。最佳实践从 Stub 起步。只实现你需要的模块。Os/Stub 后端对未实现服务返回NOT_SUPPORTED允许你增量地把 OSAL 跑起来。以 Os/Stub/Mutex.hpp 为例StubMutex用一个std::atomicbool m_mutex_taken记录状态永不阻塞适合无竞争环境或用于尽早验证接口接线。一致地映射错误码。创建一个共享的错误转换模块类似 Os/Posix/error.hpp/Os/Posix/error.cpp把目标 OS 的原生错误码转换为 F´ OsStatus枚举。仓库中 POSIX 后端对 Mutex 的映射为0 → OP_OK、EBUSY → ERROR_BUSY、EDEADLK → ERROR_DEADLOCK、其余 →ERROR_OTHER见 Os/Posix/error.cppFile/FileSystem/Directory/Task 等各有独立映射函数。集中管理错误转换使其可测试、可复用。注意句柄尺寸。每个实现对象必须放入定长的句柄存储。若目标 OS 原生原语体积较大在平台配置中覆盖FW_MUTEX_HANDLE_MAX_SIZE或对应服务的等价常量。所有句柄存储类型集中在 Os/Os.hpp 定义如MutexHandleStorage[FW_MUTEX_HANDLE_MAX_SIZE]、TaskHandleStorage[FW_TASK_HANDLE_MAX_SIZE]等各服务各有对应常量。以其他实现为参考。Os/Posix 是 F´ 中最完整的 OSAL 实现是所有模块的规范范例Os/Stub则展示了最小化实现。用 F´ OSAL 单元测试验证。框架在Os/test/ut下提供了跨操作系统的通用单元测试覆盖 OSAL 各接口运行这些测试可验证你的实现符合契约。参考资源OSAL 软件设计文档SDD — 架构细节与设计依据委托模式、链接期/编译期选择机制、错误处理、初始化等。开发一个 F´ 库 — 库结构、工具链与平台文件。移植指南 — 将 F´ 移植到新平台的高层清单。POSIX OSAL 实现 — POSIX 系统的参考实现含错误码转换、各服务实现与 CMake 注册。Stub OSAL 实现 — 返回NOT_SUPPORTED的最小实现适合增量开发。OSAL 通用单元测试 — 跨平台接口契约测试。社区平台支持包Zephyr、VxWorks、FreeRTOS 等提供了各自 RTOS 下的 OSAL 实现可作为移植更多实时操作系统的直接参照。【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考