十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RTOS抽象层的陷阱:为什么统一封装反而毁掉实时性

RTOS抽象层的陷阱:为什么统一封装反而毁掉实时性 先聊个我踩过的坑。之前在一个量产项目里我们团队在GD32F103上移植RTOS当时负责人觉得“以后换芯片、换内核肯定会很频繁”于是抽了三周时间设计了一个覆盖任务创建、信号量、消息队列、事件组、互斥锁的统一抽象层。接口全部仿照CMSIS-RTOS风格文件名叫hal_rtos.c。结果呢过了三个月需求变了要把MCU从GD32F103换到另一款Cortex-M4芯片RTOS也从FreeRTOS换成RT-Thread。业务代码确实没改一行——但整个系统变得又慢又难查问题关中断时间从原来的1.5微秒变成了6微秒电机控制环路的抖动直接从可接受范围飘到了“肉眼可见”。那段时间我天天蹲在示波器前看GPIO翻转波形最后顿悟了一个道理把RTOS抽象成一层“什么都能跑”的通用接口听起来很美实际是个巨大的坑。今天这篇就聊聊为什么我认为“RTOS抽象是错的”以及如果不用抽象层我们应该怎么设计嵌入式软件架构。1. 为什么我不建议给RTOS做“统一抽象层”1.1 抽象层想解决什么问题现实又是什么问题先说清楚这个观点不是“嵌入式不能做分层设计”或者“代码不该复用”。而是我反对“面向抽象编程、把RTOS API再封装一层通用壳”的做法。RTOS抽象层最初冒出来的动机很好理解业务代码不想绑死在某个具体内核上。早期嵌入式团队从uC/OS-II迁移到FreeRTOS或者从FreeRTOS切换到RT-Thread发现所有task、queue、semaphore相关的调用都要重写一遍工作量巨大。于是一批工程师参考Linux VFS的思路引入类似rtos_task_create、rtos_delay_ms、rtos_sem_take这样的中间接口。界面一统一换内核就等于换一个实现文件看起来确实干净利落。但问题是RTOS之间真正的差异不在API签名而在API背后的语义和调度行为。这套抽象层在编译期越成功运行期就越危险信号量计数值的初始化行为不同。互斥量是否支持优先级继承处理策略完全不同。消息队列是“拷贝”还是“引用”决定了内存生命周期归谁管。事件组是等“任意事件”还是“全部事件”不同内核的唤醒条件不一样。中断上下文里哪些API能被调用各RTOS的约束范围不一致。抽象层把这些差异全部抹平成一套接口程序员自然以为“行为也一样”于是业务代码里依赖了某个RTOS特有的微妙特性。换内核时编译器不会报错但运行逻辑已经悄悄变了。低概率的竞争条件、潜在的优先级反转、莫名丢失的唤醒事件全是这么来的。我在一个量产节点遇到过特别典型的例子用FreeRTOS写好的代码用手写的抽象层一包完全不变迁到某国产RTOS上结果系统跑几分钟就偶发死锁。排查了两天发现是抽象层把“从ISR里give信号量”和“从任务里give信号量”合并成了一个函数底层实现随意选了不用FromISR尾缀版本。在FreeRTOS里这个调用在中断上下文不支持但编译没问题只在执行时才触发断言崩溃。抽象层把这层约束隐藏掉了等于把Trigger借力给了bug。1.2 一个“最正确”的抽象层最终会退化成“最差内核”回到设计理论抽象层要保证通用只能取所有内核的“最大公约数”。这听上去合理实际是在向下收割性能与功能。举几个我实际跨内核验证过的活生生例子调度策略FreeRTOS默认是固定优先级抢占式调度时间片切片可以开关。RT-Thread的定时器调度粒度更细还有软定时器和信号机制。你要抽象统一只能保留“创建任务-挂起-恢复-延时”这些最基础能力。优先级继承哪些内核做得好任务级的CPU亲和怎么表达统统丢了。内存模型FreeRTOS的heap方案是pvPortMalloc统一分配任务栈、队列、信号量对象全在heap里ThreadX的byte pool机制更复杂Zephyr更狠大量使用静态定义和编译期分配。你希望模块能在Zephyr上编译抽象层就不能写一个“任务栈由内核动态分配”这样的实现因为Zephyr不是这么玩的。强制统一的结果就是要么实现非常别扭要么运行时开销暴涨。中断处理模型有些RTOS把中断服务分成“快中断”和“慢中断”有些只有单一入口有的允许中断里调用大部分API有的要求ISR必须用特殊函数。抽象层要兼容只能把API接口卡片化底层做分支判断结果就是每次调用都多一层跳转、多一堆分支预测开销。最糟糕的“公共分母”化发生在IPC上。比如你要抽象“信号量”但某个RTOS的信号量semaphore只支持二进制模式另一个支持计数模式抽象层就只能按二进制实现。业务需要计数器时你还得另找消息队列方案。最后做出来的东西性能取各内核最低值功能取交集调试体验还差到极点——你都不知道自己的代码跑在哪个内核的哪个实现上。1.3 性能损耗与调试地狱需要直面而不是回避如果只是“慢一点”我可能还会勉强容忍。但RTOS抽象层带来的性能回归常常是致命的。以我实测过的Cortex-M3/M4为例一次带参的任务切换FreeRTOS原生开销大概是2到4微秒取决于优化等级和是否开启FPU上下文保存。加上抽象层后光这个外壳的跳转、参数校验、状态记录就可能额外吃掉1到3微秒。单次调度看不出问题但在125kHz控制环、4kHz中断源并存的系统里这些微秒就是能把你逼疯的噪声源。更麻烦的是临界区或关中断时间长。抽象层喜欢在API入口做状态记录、断言检查、时间戳记录这些操作在进入真正内核函数前还要不要关中断如果你不想吞掉中断就加了一堆原子操作、嵌套互斥。我实测过有的抽象层一个rtos_mutex_lock函数临界区时间从原生互斥量的1微秒拉升到7微秒。对电机换相、飞控控制环来说中断关了7微秒基本等于放弃了实时性预算。调试场景也一样。本来RTOS的堆栈回溯就很复杂抽象层加进来后调用链更深调试器里看到的是层层嵌套的shell函数你很难一眼看出当前处于哪个任务、准备调度到哪个任务。如果你还用了事件跟踪工具比如SEGGER SystemView、Tracealyzer还得给抽象层额外做插桩否则RTOS内核本身的trace信息依然存在但业务逻辑的“真实意图”被隔离在外层分析起来基本等于盲人摸象。2. 深入剖析RTOS抽象失败的根源在哪2.1 调度语义的异构性是抽象层最棘手的敌人要说RTOS抽象“为什么错”的根源我脑子里蹦出来的第一个词就是“语义鸿沟”。很多工程师把RTOS API理解成“函数签名一致就行”但操作系统本质是一个调度状态机。不同的内核对“何时调度、谁先就绪、优先级翻转怎么处理”这些问题的答案天差地别。统一外壳解决不了内核内部的调度算法差异。比如优先级翻转。FreeRTOS的互斥锁自带优先级继承RT-Thread也支持mutex优先级继承可如果你抽象的是“信号量”而不是“互斥锁”优先级继承就没了。业务代码里写着“这只是一个锁”底层实现却是一个二元信号量那高优先级任务被低优先级任务长时间阻塞的场景会是什么后果想象一条生产线上总经理找基层员工盖章结果厂长夹在中间还不上心总经理只能干等——你的实时系统就在这种状态下挣扎。函数的签名看起来一样心态上却完全是两个物种。再比如说延时精度。FreeRTOS的vTaskDelay用的是tick计数默认配置下1毫秒一次tickRT-Thread的软定时器精度可以被配置到微秒级。抽象层通常只保留“延时毫秒”这个面结果就是毫秒以下精度的控制逻辑根本得不到保证。你只觉得自己的线程卡顿了却不知道是这个“统一接口”把内核的能力阉割了。2.2 内存模型与对象生命周期的差异第二个根源是内存管理模型差异巨大。嵌入式RTOS的对象生命周期管理是底层硬约束的体现。FreeRTOS用动态内存堆Task/Queue/Semaphore都可以在运行时创建任务栈动态分配。Zephyr更倾向编译期静态定义线程栈、消息管道都是K_THREAD_STACK_DEFINE写在代码里。运行时创建有但很多项目为了安全直接禁用动态内存。ThreadX有自己的byte pool机制对象池化管理。LiteOS的驱动框架和内核对象关系更紧密很多对象直接绑定在驱动模型上不能随便剥离。一个漂亮的对业务模块通用的抽象层必须支持“运行期创建任意内核对象”模型否则很多依赖动态创建的任务就写不下去。可你一旦选择了这种方式就意味着你在Zephyr上跑动态创建、在静态内存模型的项目里偷偷调用k_malloc或者在一大块固定的RAM里自造对象池。这种实现方式不仅破坏了目标RTOS的安全设计理念还让系统的静态分析形同虚设。真正安全、可审计的嵌入式代码不应该在某个抽象层下暗度陈仓。生命周期也一样。在FreeRTOS里删除任务后任务栈由内核自动释放在Zephyr里线程栈如果是静态定义的就不会被自动清理模块卸载时得手动处理。抽象层把“销毁对象”包装成一个函数业务层以为对象没了底层内存却还在泄漏或者刚刚被释放等待复用。这种用久了才暴露的问题最难查。2.3 驱动模型与板级差异抽象层很难覆盖另一个经常被忽略、但其实特别伤的问题是抽象层跟“设备驱动模型”的相互作用。热搜词里就有“liteos rtos驱动开发”和“zephyr rtos”——这两个体系的驱动模型风格差异极大抽象层叠加在它们之上基本是一种灾难。以Zephyr为例它有一套完整的基于设备树devicetree和驱动框架的模型外设访问方式、DTS绑定、API结构都有严格规范。如果你把Zephyr的驱动抽象成“直接读寄存器”那等于把Zephyr最精华的板级可移植性全都毁掉。反过来看LiteOS它的驱动框架绑定LiteOS内核的消息机制和内存池你要在它的底层再包一层通用RTOS抽象那驱动本身的流程比如中断回调、buffer管理就全都绕不开抽象层性能损耗直接翻倍。所以我常说RTOS抽象层往往能抽象“任务的壳”却抽象不了“驱动的魂”。驱动模型的差异就是处理器和外设世界的差异。你不抽象直接面对不同驱动框架还能接受一抽象所有硬件细节都挤进一个看似“平台无关”的黑盒一旦外设时序出问题你连问题的入口都找不到。2.4 不要再拿“RTOS和Linux的区别”来类比抽象层很多支持者会说“Linux不是也把调度器、文件系统、设备驱动抽象得干干净净吗为什么RTOS不行”这个问题其实问得特别关键。答案是RTOS的设计目标跟Linux完全不同。Linux追求的是大规模资源管理、多用户安全隔离、复杂的调度和内存管理。它靠抽象层把精细策略藏起来是合理的因为Linux应用层甚至不知道有进程调度这回事。RTOS的目标恰恰相反是“在尽可能短的时间内对已知事件做出可预测的响应”。RTOS的用户嵌入式开发者希望知道每一个内核调用什么时候会发生调度、关中断多长时间、最坏响应时间多长。这些信息全在“具体实现”里而不在“抽象接口”里。抽象层在Linux里是“把复杂度关在门里”在RTOS里却等于“把复杂度拿到门外”。它增加了不确定性破坏了可预测性。那就是错的。3. 实测对比同样业务用抽象层和不用抽象层的区别3.1 一组来自GD32F103项目的真实数据为了不让所有讨论都停留在口嗨阶段我拿之前GD32F103CBT6量产项目的数据来说话。芯片主频72MHzCortex-M3内核编译环境是ARM GCC -O2RTOS选用FreeRTOS V10.4.6。我们的核心控制任务一个是1kHz的电机控制中断一个是10ms的CAN报文处理线程还有一个50ms周期的人机交互任务。当时对比了两种代码形态形态A业务代码直接调用FreeRTOS原生API。形态B业务代码调用自研的rMcuOS抽象层接口底层再映射到FreeRTOS。同样的业务形态B的代码体积增加了大约18%主要来自函数外层包装和断言逻辑RAM占用增加了5%抽象层对象簿记和调试缓冲这些都是小头。真正的大头是性能任务切换过程中形态A的平均切换时间在2.8微秒形态B直接变成4.5微秒。一个高频信号量的take/give周期形态A是1.6微秒形态B是3.2微秒。更惨的是关中断时间。形态A里互斥锁的最长关中断时间约1.2微秒形态B因为外层还有状态记录和函数调用最长关中断时间到了6.5微秒。1kHz的电机控制中断虽然还活着但中断响应抖动从±3微秒扩大到±18微秒。我们用示波器抓GPIO翻转已经能看到明显的波形抖动这在以前绝对不敢想象。你可能会问产品经理要求“将来换RTOS不重写代码”怎么办我的回答是从GD32F103换到同系列F4甚至STM32F4FreeRTOS都能移植业务代码本来就不用改。真正需要换RTOS的场景很少而且当你确实需要换时正确的做法是把“业务架构”和“RTOS API”分离而不是用一层通用接口强行压住。3.2 信号量、队列和事件组抽象层里最容易出错的一环再讲三个我在项目中反复撞墙的经典问题信号量的“最大计数值”边界。FreeRTOS的SemaphoreHandle创建时Count可以配置为任意数值但计数有上限RT-Thread的信号量sensitive也有sensitive-max值。抽象层只暴露sem_create(init_count)是不够的你不小心在多处give信号量计数涨到超出预期直接翻转成另一个逻辑错误。业务上处理得很小心还好可一旦抽象层隐藏了“上限”概念这种错很难查。消息队列是拷贝还是引用。FreeRTOS的队列默认是值拷贝数据一次性塞进队列内部缓冲区部分轻量级RTOS的queue是传指针。业务代码如果按照“拷贝”写往队列里丢局部变量换成一个传引用的RTOS那么这个局部变量出作用域后内容就可能变了。你期望的是发送一份独立数据底层却只是通知你“来取我告诉你的地址”等接收端取的时候数据早没了。这种bug不靠抽象层查不出来代码review也看不出问题。事件组的等待模式差异。有的内核支持“或者”等待与“并且”等待有的内核还需要额外一次“清除事件”的调用才能重新等。抽象层的包装可能把清除逻辑固化导致某些场景下事件状态错误。我一次在双消息触发场景里事件组偶发丢失一个触发源最后排查发现抽象层把两个事件的读取放在一个非原子操作后面第二个事件状态还没被置位就返回了。这些坑如果你不抽象大多靠读内核文档就能规避一旦抽象错误就像被埋进了水泥里不把整个外包装敲掉根本看不见。3.3 LiteOS和Zephyr的驱动开发不能承受的“再抽象”我还试过在LiteOS上做驱动开发时引入抽象层。结果是驱动里需要调用LiteOS的消息队列、内存池和事件机制如果再用自研接口包一层整个驱动的“hot path”高频数据收发路径将被拉长至少1倍。最终驱动物理层没问题应用层却总出现数据跟不上就是延迟惹的祸。在Zephyr上更别说了。Zephyr的设备驱动模型依赖设备树所有外设访问都通过device_get_binding、i2c_configure、gpio_pin_configure这类高层API。假如你在Zephyr里再用一套“RTOS抽象层”去包驱动接口那驱动本身的实现都得先绕过Zephyr的框架再绕回你自定义的抽象才能真正控制硬件。Zephyr的设备树机制和驱动模型本身就是一套成熟的抽象它面向的是“硬件可移植性”而不是“RTOS可移植性”。你在它上面再加RTOS抽象简直是在抽象上再叠抽象性能、复杂度、维护性全面崩溃。4. 正确的做法不做“抽象层”做“层内架构”4.1 用“分层隔离”替代“接口统一”那肯定有人问不做抽象层我们怎么解耦总不能每个模块都直接写xTaskCreate、osThreadNew吧。我的答案是把RTOS当成项目的一部分而不是一个可以随时拔插的“通用组件”。在设计架构时我不做rtos_task_create这种跨越所有RTOS的统一接口而是把代码分成“硬件无关模块”和“硬件相关模块”在模块边界用自定义的“短接口”来清理依赖。举一个我比较习惯的模板。比如一个电源管理模块它不需要知道自己是跑在FreeRTOS还是RT-Thread上它只需要“周期调用”、“收到外部事件时唤醒”、“持锁后更新共享数据”。那我给它定义的接口是typedef struct { int (*init)(void); int (*start)(void); int (*on_event)(uint32_t event_mask); int (*set_voltage)(uint32_t mv); } power_manager_ops_t;这个接口不是RTOS API的重复而是业务模块自己的需求描述。那在具体RTOS上start可以由一个FreeRTOS的task函数实现也可以由一个RT-Thread线程函数实现甚至可以是一个裸机main loop里的状态机函数实现。业务模块面向的是“我需要被周期性执行、我可以等待事件”而不是“我应该调用哪个RTOS的哪个API”。这种模式有名字经常被称为“模块边界接口”或“Port接口”本质上也是一种抽象。但它跟RTOS抽象层有个决定性的区别它只抽象“业务需求”不抽象“OS能力”。这样即使底层从一个RTOS换到另一个你只需要重写这个模块对应的“适配文件”业务逻辑完全不用动。而我做RTOS抽象层时是在抽象“RTOS的API”为了“接口长得像”而牺牲行为一致性和性能——核心目的搞错了。4.2 实践路径先从“评估内核特性”开始而不是从“封装API”开始具体落地时我建议按这个顺序操作先定硬件和RTOS。GD32F103这类Cortex-M3跑FreeRTOS和RT-Thread都没问题。选择时重点考察中断模型、内存量、对时序的要求。别一开始就搞“以后可能换”除非你有确凿证据未来真的会换。再做模块划分。哪些模块是纯算法逻辑比如PID控制、协议解析它们根本不依赖RTOS哪些模块需要周期性执行、事件通知、互斥访问它们需要某种形式的并发原语。把“依赖点”列出来而不是把所有代码都默认绑在RTOS上。针对依赖点写“短接口”。注意是“短”接口不是“全”接口。该电源模块就是init/start/on_event/set_voltage四个函数该传感器模块就是read/configure两个函数。接口越短适配成本越低越难犯大错。在每个具体RTOS里实现这个接口。比如在FreeRTOS里start就是xTaskCreate之后调用vTaskStartScheduler在裸机环境里start可能就是while(1)循环加状态机跳转。接口背后用什么机制来保证互斥/同步由当前RTOS的实现决定不需要给外部承诺。保留一张“行为契约表”。不是API签名表而是“最坏情况下的延迟”、“任务优先级安排”、“中断优先级安排”、“栈大小估算”。这比任何抽象层都更能保证系统可靠。用这个思路我后来做的一个产品从FreeRTOS迁移到RT-Thread业务模块只改了适配文件、配置脚本和启动代码真正业务算法文件几乎没动大概一个下午就迁移完了。没必要为了这种“低频需求”付出永久的性能和调试代价。4.3 团队规范不做“万能ROOT”但做“模块边界ROOT”在团队协作里比抽象层更能提升效率的是“模块边界清晰 接口文档精确 关键路径禁止外壳”。我见过团队里最乱的项目不是没抽象层而是每个成员各写各的“RTOS工具函数”——今天你包一个create_task明天他包一个send_msg后天又有人做了第三个功能重叠的版本。最后整个代码库到处都是“小抽象层”比一个大的还可怕。所以我的规范是每个模块只能通过自己定义的接口访问内核且接口数量尽量少。公共的基础设施比如日志、统计、错误处理可以单独成一个基础库。但它不该包装RTOS的API而是包装“业务行为”。严格禁止在业务模块里直接出现osDelay、xTaskDelay这类与内核绑定的调用。你要延时可以调用模块自己的xxx_wait(timeout)由实现者决定这个wait是用内核延时、定时器还是忙等。对所有涉及中断、临界区的代码必须在注释和文档里写明“此处可能关中断的时间量级”让后来人知道代价。这样团队里的“抽象”就产生了不是抽象RTOS而是抽象“业务的执行需求”。RTOS本身是研发的底座而不是一个需要被“伪装成其他人”的中间件。4.4 关于RTOS面试题面试官问你“RTOS抽象层”时该怎么答热搜词里有“rtos面试题”顺便聊聊这件事。我当过不少嵌入式岗位的面试官如果候选人说“我做了一个RTOS抽象层封装了所有内核API”我先不否定但一定追着问“你在抽象层里如何处理FreeRTOS和RT-Thread互斥量优先级继承的差异”“换内核以后你的消息队列语义有没有变化你怎么保证不变”“抽象层加了多少开销你测过没有”如果候选人的回答是“这些不重要我们只需要统一的接口”我基本会给中等偏下的评价。因为这说明他没理解RTOS的抽象层次。更好的回答是“我认为给具体RTOS再做统一抽象层不值得。项目优先选择一个内核然后用模块边界接口来隔离业务需求如果未来真的要换内核只需要重写适配层。我会确保接口极简语义明确并且用性能测试来验证底层切换对实时性的影响。”这个回答首先展现出对RTOS底层差异的理解其次说明自己在设计上有架构意识还有实操方案。比单纯亮一句“我封了所有API”有说服力得多。5. 常见问题与排查技巧实录5.1 抽象层时代最经典的三类诡异Bug我把自己和身边朋友踩过的坑整理成一张速查表帮助有同样困惑的人少走弯路现象真正原因排查方法事件组偶发不唤醒任务抽象层把“等待并清除事件”实现成“先读后清”两次调用之间事件丢失用RTOS原生API重写同场景跑压测对比行为队列数据读到“半新半旧”抽象层用“引用传递”实现队列底层太早释放buffer查看内核源码确认每个API的拷贝/引用策略任务偶尔死锁复位后正常抽象层合并了mutex和sem优先级继承失效用Tracealyzer抓锁等待图检查相互持锁路径定时器回调跑飞抽象层把软定时器回调放进了ISR上下文关闭抽象层直接用目标RTOS原生定时器API验证中断响应时间突增函数入口大量日志/断言临界区过大关掉抽象层断言和日志重新测tick抖动这张表的核心结论就一句话当系统行为异常时第一件事是“拆掉抽象层”用原生API重现逻辑。如果原生API下问题消失问题一定出在抽象层而不是RTOS。反过来如果原生API下问题依旧那才是真的业务逻辑或配置问题。5.2 调试技巧用原生API验证“抽象层没捣乱”我自己的调试方法论是任何新功能上线前都先用RTOS原生API写一遍相同的“最小复现用例”跑完所有边界条件再套进抽象层如果项目里暂时还有人忍不住用。这样能快速区分“抽象层行为不一致”和“业务逻辑有缺陷”。常用手段还有一个在每个抽象层函数的入口和出口打一个GPIO翻转接逻辑分析仪。如果入口到出口的时间超过预期说明该内核调用路径太长或者抽象层里有隐藏的关中断/忙等。这个方法帮助我抓出过好多“莫名奇妙”的延迟问题不需要昂贵的trace工具。还有一个容易被忽视的细节检查RTOS的“钩子函数”。很多RTOS都有idle hook、tick hook、assert hook。抽象层如果想当然地占用了这些钩子可能跟业务模块互相覆盖。如果你发现系统时而正常时而崩溃直接去查hook函数是不是被抽象层或业务代码“双写”。5.3 如果你的项目已经被“抽象层”绑住了怎么逐步退出有些朋友会说“道理我懂了但项目已经存在一个抽象层几千个文件都include了它怎么退出”我的建议是“分层蚕食”不要一步推翻冻结抽象层接口不允许再新增抽象接口新代码直接用具体RTOS原生API。在关键路径上绕过抽象层高频中断、控制环、IPC热路径改成直接调用内核API并记录性能差异。把模块边界接口引进来在抽象层的上面为那些纯业务模块定义极简的短接口让它们慢慢脱离对外壳的依赖。逐步替换测试每次替换几个模块跑一轮长时间压测对比替换前后的中断抖动、任务切换时间、内存占用。最终把抽象层降级成一层薄薄的“兼容垫”只保留少量工具函数比如日志、断言、复位原因读取而不是内核API封装。这个过程我做过两次最长的一次是一个老产品花了两个月才把核心业务完全从抽象层中剥离出来。但剥离之后系统中断响应时间改善了大约40%内存也省出了接近10%效果极其显著。所以即使已经“上错船”也永远来得及靠岸。写在最后从那次GD32F103项目之后我的设计原则那次项目结束以后我给自己定了几条硬性原则。第一条绝不写“覆盖所有RTOS功能”的抽象层我只在业务模块边界写“刚好够用”的短接口。第二条选择和换内核时优先评估“调度语义、内存模型、驱动框架、中断模型”而不是对比API函数名。第三条每次架构评估都要准备一个“用原生API实现”的最小对照组用来验证抽象层或分层设计是否引入了不必要的性能损耗。第四条团队文档里明确每个模块对RTOS特性的依赖点一旦发现某个模块过度依赖某内核特性就要考虑这个模块是否需要独立出来。有时候也会有人拿“产品将来要跨平台、跨芯片”来逼我做抽象。我的回答是跨芯片靠的是HAL和驱动框架跨RTOS靠的是模块边界接口而不是把内核API包一层就跨了。抽象层的核心价值是“屏蔽差异”但如果它屏蔽掉的是“实时约束”的差异那它带来的就是“灾难”而不是便利。RTOS抽象是错的。真正对的做法是让业务模块说自己的语言请一个翻译去翻译给RTOS听而不是让所有模块学会说同一种外语再靠那个翻译把话传歪。
返回列表