
手上这块 GD32F103VET6 开发板从去年电机控制项目一直用到现在前前后后跑过八款 RTOS。起因挺简单网上关于 MCU 上各款 RTOS 谁快谁慢的说法太乱同一个 FreeRTOS 的上下文切换时间有人测出 1.2 μs有人测出 5 μs还有人拿 STM32F407 的测试数据去对比 GD32F103 的结果最后得出一个某某 RTOS 比某某快三倍的结论。这种横评我看了心里发痒因为我知道只要编译器优化等级差一级、SysTick 频率差一倍结论就能翻过来。所以这回我把八款 RTOS 全部丢到同一块 MCU、同一套基准代码、同一台逻辑分析仪上跑了一遍把每一处可能造成误判的环节都单独标出来。这篇东西不是排行榜而是一份怎么测才不被骗的记录。如果你正在给 MCU 选型、正在准备 RTOS 相关的面试、或者刚把 GD32F103 移植 RTOS 跑通想看看性能到底什么水平那下面的内容可以直接抄。数据是我的板子上的数据绝对值会随芯片、编译器、Flash 等待周期变化但测量方法和那些坑换任何平台都成立。1. 横评这件事最怕的就是变量没控住1.1 为什么非要同一块 MCU做性能对比第一原则是只留一个自变量。我见过太多对比帖里FreeRTOS 跑在 168 MHz 的 F4 上RT-Thread 跑在 72 MHz 的 F103 上然后得出结论说 FreeRTOS 快一倍——这个结论的置信度等于零因为主频本身就差了 2.3 倍内核架构也从 M3 换成了 M4还多了指令和数据 Cache、ART 加速器。这两个东西对上下文切换这种小函数、频繁跳转的场景影响巨大M4 的取指速度和 M3 完全不是一个量级。所以我坚持用同一块板子。GD32F103VET6Cortex-M3 内核主频固定 72 MHzFlash 容量 512 KBSRAM 64 KB没有 Cache、没有 ART、没有指令预取加速的差异。这样做的好处是所有 RTOS 面对的是同一个取指通路、同一个 SRAM 读写速度、同一套 NVIC 优先级分组测出来的差异只可能来自 RTOS 自身的实现。代价也有就是这套数据的适用范围只到 Cortex-M3 这一层。M0 没有硬件除法、没有位带之外的优化切换时间普遍比 M3 慢 30%~60%M7 带 Cache切换时间甚至能压到 M3 的一半以下。所以看数据的时候心里要有一根弦内核不同排名会变尤其是那些依赖汇编优化的端口实现。1.2 八位选手的名单和入选理由选 RTOS 的时候我定了三条标准一是在 Cortex-M3 上有成熟的官方或社区端口二是能在这个 64 KB SRAM 上跑起来三是资料足够多、实际项目里真的有人用。按这三条筛完剩下八个编号RTOS版本类型定位典型使用场景1FreeRTOSV10.4.6精简内核通用生态最广2RT-Thread Nano3.1.5精简内核小资源 MCU3RT-Thread5.0.2完整系统带设备框架和组件4μC/OS-IIIV3.08.01认证级内核工控、医疗5ThreadX6.1.9高实时内核消费电子、工业6Zephyr3.5.0完整系统物联网、多协议7LiteOS-M开源主线轻量内核端侧设备8TencentOS tiny2.4.2轻量内核物联网终端这里必须说明一点RT-Thread Nano 和 RT-Thread 完整版我拆成两个来测因为前者只有一个调度器加少量 IPC后者带了设备框架、FinSH 命令行、软件定时器、内存管理器一大堆东西把它们放一起比较毫无意义。同样Zephyr 我关掉了绝大多数子系统只留内核和必要的驱动即便如此它的最小镜像还是比 FreeRTOS 大好几倍。这不是谁好谁坏的问题是设计目标本来就不同。1.3 三组指标分开看才不会被带偏如果只测一个切换时间很容易得出偏颇结论。我定了三组指标每组回答不同的问题第一组是纯调度开销包括任务间 yield 的往返时间、信号量 ping-pong 延迟、消息队列投递延迟。这组数据反映内核核心路径的执行效率是面试里最常被问到的东西。第二组是实时性指标包括中断响应延迟硬件层面的东西各家差异其实很小以及中断发生到高优先级任务真正开始运行的端到端延迟。后者才是真正影响产品体验的数字但因为测量复杂很少有人在对比里给出。第三组是资源占用包括最小可运行工程的 Flash 和 RAM 占用、每新增一个任务的边际开销、可选组件带来的膨胀。MCU 项目里资源紧张的时候这一组往往比速度更重要。提示把这三组混在一起谈谁最快是典型的误判来源。一个内核可以在切换速度上排第一同时因为内存管理器的实现导致中断到任务的唤醒延迟排倒数这不是矛盾是设计取舍。2. 测试平台搭建与基准代码工程2.1 硬件清单与接线方式硬件部分不复杂列一下主控板GD32F103VET6 最小系统板外部 8 MHz 晶振倍频到 72 MHz调试器CMSIS-DAP 兼容调试器SWD 四线测量设备8 通道逻辑分析仪采样率 100 MS/s时间戳分辨率 10 ns辅助设备台式示波器探头带宽 100 MHz用于交叉验证逻辑分析仪的边沿时间电源USB 供电改线性稳压避免开关电源纹波影响 ADC 相关测试接线是这次测量的关键。我用 GPIOB 的三个引脚作为探针PB0 标记任务 A 的活动区间PB1 标记任务 B 的活动区间PB2 由中断服务函数第一行翻转用来测中断延迟。三个引脚直连逻辑分析仪的三个通道走线尽可能短避免长线带来的边沿变缓。为什么用 GPIO 翻转而不是用 DWT 的周期计数器因为 DWT 计数器需要额外代码去读、去存、去打印这些操作本身就要几十个周期而且不同 RTOS 里访问 DWT 的路径可能被调度器打断。GPIO 翻转只有一条写寄存器的指令如果用位带操作甚至只有一次读改写干扰最小。GPIO 的时序也不会骗人逻辑分析仪抓到的就是真实的时间戳。2.2 测量方法往返时间和单次时间必须分清这是全篇最容易被误判的一点我单独拎出来讲。常见做法是开两个同优先级任务任务 A 先翻转 GPIO 置高然后调用 yield 让出任务 B 运行后翻转 GPIO 置低再 yield 回来。逻辑分析仪测到的高电平宽度就是从任务 A 调用 yield 开始到任务 B 执行到翻转那一行的总时间。这个时间包含两次上下文切换A 切出、B 切入。所以它是往返时间不是单次切换时间。网上一大堆对比数据把往返时间当成单次切换时间发出来直接导致结果虚高了一倍。我在下面的数据表里两个都列你自己按需取用。那单次切换时间怎么算严格来说要从往返时间里减掉任务 B 里从恢复到执行 GPIO 翻转这一段代码的执行时间。我的处理方式是写两个版本的测试任务一个版本在 yield 之前翻转引脚另一个版本在 yield 之后立刻翻转两个时间相减就能把用户代码的开销剥掉。说起来绕做起来就是编译三次、跑三次、取平均。2.3 八款 RTOS 在 GD32F103 上的移植要点GD32F103 和 STM32F103 在外设寄存器层面高度相似所以移植的基本思路就是拿 STM32F103 的端口改启动文件和时钟配置。但每个 RTOS 的坑都不一样我逐个说。FreeRTOS 的移植最省事直接把port.c和portmacro.h拿过来改一下configCPU_CLOCK_HZ和configTICK_RATE_HZ就能跑。要注意的是中断优先级分组必须设成 NVIC_PRIORITYGROUP_4也就是全部 4 位都给抢占优先级否则configMAX_SYSCALL_INTERRUPT_PRIORITY的配置会失效出现在中断里调用 FromISR 接口导致硬件错误的经典问题。RT-Thread Nano 走的是包管理器路径如果你想手工移植需要自己实现rt_hw_board_init、rt_hw_console_output和 SysTick 中断处理。第三个最容易漏漏了以后系统能启动但时间片永远不变。RT-Thread 完整版建议直接用官方 BSP 里的 STM32F103 工程改名改时钟树和串口引脚。它的board.c里已经包含了时钟初始化和堆初始化改错一处就会卡在rt_hw_board_init里出不来。μC/OS-III 需要写os_cpu_c.c和os_cpu_a.asm两个文件前者实现钩子函数不用就留空后者是 PendSV 和 SysTick 的汇编入口。这一块用 AC6 编译器时要注意汇编语法从 armasm 换成了 GAS 风格直接拿老工程编译会报一堆语法错误。ThreadX 的移植量比想象中小核心就是tx_initialize_low_level里配置 SysTick 和向量表再提供_tx_thread_system_stack_ptr等几个符号。它的tx_port.h里有一堆可配置项默认值偏保守改完能省 20% 左右的切换开销。Zephyr 建议直接用 west 工具初始化工程手工移植会痛不欲生。它的设备树和 Kconfig 体系需要先熟悉prj.conf里关掉不需要的子系统能把镜像从 200 KB 压到 40 KB 出头。LiteOS-M 和 TencentOS tiny 都提供了 STM32F103 的参考工程改时钟和外设地址即可移植量和 FreeRTOS 差不多。心得移植阶段不要急着测性能先让串口能正常打印、系统能稳定跑够 24 小时不重启。我第一轮测试的时候有个内核因为栈配置小了 64 字节跑十几分钟就飞结果我把采样窗口设成 10 分钟测了三次都正常白折腾了一晚上。3. 实测数据与逐项拆解3.1 任务上下文切换时间先说测试配置两个同优先级任务时间片轮转关闭手动 yield。编译器统一用 AC6优化等级-Os链接脚本统一堆栈大小统一给 512 字节。每项测 1000 次去掉最高和最低的 5%取中位数。RTOS往返时间 (μs)推算单次切换 (μs)折算周期数 (72MHz)裸机基线直接函数调用翻转0.42—30ThreadX 6.1.92.811.1986RT-Thread Nano 3.1.53.321.45105FreeRTOS V10.4.63.641.61116TencentOS tiny 2.4.23.981.78128RT-Thread 5.0.24.211.90137LiteOS-M4.632.11152μC/OS-III V3.08.015.382.49179Zephyr 3.5.05.922.76199裸机基线那 0.42 μs 是测量链路本身的底噪包含两次 GPIO 写、两次函数调用、循环变量的自增判断。也就是说真实的切换时间应该从上表数字里再扣掉这一部分ThreadX 大约 1.0 μsZephyr 大约 2.6 μs。这个扣除不是吹毛求疵在 2 μs 量级的测量里0.4 μs 已经是 20% 的偏差了。为什么 Zephyr 和 μC/OS-III 偏慢看汇编就清楚了。Zephyr 在 Cortex-M3 上默认开启了一堆内核对象统计和线程监控_Swap路径里要维护thread-base.usage之类的结构体字段多出来的十几条指令全是内存读写。μC/OS-III 则是因为它的任务控制块设计得更厚上下文保存时要把更多的寄存器压栈而且它的就绪表操作比 FreeRTOS 用的位图查找多了一层分支。ThreadX 快在哪它的 PendSV 处理函数写得非常克制寄存器压栈用的是 STMDB 批量指令就绪队列用的是双向链表加位图两级查找路径上没有多余判断。这是我实测下来切换最快的一款和它在工控圈的口碑对得上。3.2 信号量和互斥量的唤醒路径开销信号量 ping-pong 测试是这么做的低优先级任务释放信号量后立即阻塞高优先级任务被唤醒后翻转 GPIO 再释放回去循环往复。测出来的时间包含释放信号量 触发 PendSV 切出 切入 被唤醒任务执行的完整链路。RTOS信号量 ping-pong (μs)消息队列投递 1 字节 (μs)互斥量加解锁 (μs)ThreadX 6.1.93.944.322.71RT-Thread Nano 3.1.54.625.183.35FreeRTOS V10.4.64.915.633.88TencentOS tiny 2.4.25.236.074.02RT-Thread 5.0.25.546.414.29LiteOS-M5.817.124.55μC/OS-III V3.08.016.838.045.11Zephyr 3.5.07.429.365.88有几个细节值得说。第一消息队列比信号量普遍慢 10%~30%因为队列要拷贝数据、移动读写指针、判断空满状态。如果消息只有 1 字节你可以考虑用信号量加全局变量的方式替代能省下不少时间。当然这会带来并发安全问题得自己权衡。第二互斥量的加解锁比信号量快这个结论可能和直觉相反。原因是互斥量在无竞争的情况下走的是快路径直接改一下计数器就返回不触发调度而信号量的 ping-pong 测试里必然触发任务切换。如果你测的是有竞争的互斥量数字会反过来因为优先级继承算法本身要遍历阻塞链。第三RT-Thread 完整版比 Nano 慢约 20%。这个差距主要来自完整版里多了rt_object的类型检查和钩子调用rt_sem_take里会先做一次对象类型断言。发布版本里可以把RT_DEBUG关掉能追回来一半。注意所有 IPC 测试都必须关闭内核调试和断言否则测的是调试版本数据没有参考价值。我第一次测 RT-Thread 完整版的时候忘了关测出来比 μC/OS-III 还慢查了半天才发现是断言在起作用。3.3 中断延迟两种定义两套数据中断延迟这个词被用得太随意了。我把两种都测了数据差别很大。定义一中断响应延迟指从引脚上出现中断信号边沿到 ISR 第一条指令执行的物理时间。这个时间由 Cortex-M3 内核的硬件决定入栈 8 个寄存器 12 个周期取向量 2 个周期加上 Flash 等待周期理论上应该在 0.6 μs 左右和用哪个 RTOS 几乎无关。定义二中断到任务唤醒延迟指从 ISR 里释放信号量开始到等待该信号量的高优先级任务真正开始执行第一条指令的时间。这个才受 RTOS 影响。RTOS中断响应延迟 (μs)中断到任务唤醒 (μs)Tick 抖动峰值 (μs)ThreadX 6.1.90.713.420.9FreeRTOS V10.4.60.684.151.4RT-Thread Nano 3.1.50.694.511.6TencentOS tiny 2.4.20.724.872.1LiteOS-M0.705.322.3RT-Thread 5.0.20.715.682.8μC/OS-III V3.08.010.736.142.2Zephyr 3.5.00.696.933.4中断响应延迟那一列八款之间的差异只有 0.05 μs也就是 3.6 个时钟周期基本落在测量误差范围内。这印证了一件事在 Cortex-M3 上中断入口是硬件行为RTOS 想插手也插不上。任何声称某 RTOS 的中断响应比另一个快一倍的说法要么测的是定义二要么测量方法有问题。中断到任务唤醒那一列就拉开了最快和最慢差了整整一倍。差距来自 PendSV 挂起后到实际执行之间的路径长度以及调度器找下一个任务的方式。Zephyr 在这里慢是因为它的中断退出路径要经过一层z_swap封装中间还有arch_switch的函数调用。Tick 抖动是另一个容易被忽略的指标。它反映的是系统在负载下时钟节拍的稳定性。Zephyr 的抖动最大因为它的 Tick 处理函数里做的事情多如果刚好赶上系统时钟和内核定时器的双定时器模式抖动还会更大。3.4 资源占用小资源 MCU 上这比速度重要Flash 数据来源是 map 文件的.text加.rodataRAM 来源是.bss加.data加上最小任务栈需求。所有工程都裁剪到能跑起来一个任务、能通过串口输出一行字符串的最低限度。RTOSFlash 占用 (KB)内核 RAM (KB)单任务边际开销 (字节)RT-Thread Nano 3.1.55.80.72156TencentOS tiny 2.4.26.41.05172FreeRTOS V10.4.67.20.88148ThreadX 6.1.98.61.21224LiteOS-M9.42.18268μC/OS-III V3.08.0111.32.54312RT-Thread 5.0.225.76.03248Zephyr 3.5.042.112.4356RT-Thread 完整版的 25.7 KB 里光 FinSH 命令行就占了差不多 9 KB。如果你不需要串口交互把RT_USING_FINSH关掉能直接瘦身三分之一以上。Zephyr 的 42 KB 里包含了设备树解析、日志子系统和内核对象注册裁剪到极限还能再降但那时候它和普通轻量内核的差距就没那么明显了。单任务边际开销这一列在只有 20 KB SRAM 的 MCU 上非常关键。128 KB 的片子你可以随便开任务20 KB 的片子你每加一个任务都要算一遍TCB 加栈栈还得留够中断嵌套的余量。提示Cortex-M3 上建议给每个任务的栈至少留 256 字节的安全余量尤其是跑浮点运算的任务。中断嵌套时压栈用的是当前任务的栈一旦溢出就是随机跑飞而且这种现象在调试器里极难抓。4. 谁最容易被误判八个经常踩的陷阱4.1 编译器优化等级一改排名就变我做的第一件事是把八款 RTOS 分别用-O0、-O1、-Os、-Ofast编一遍看看数据怎么变。结果很说明问题-O0下所有内核的速度都慢了一倍多而且相对排名也乱了FreeRTOS 在-O0下甚至比 μC/OS-III 还慢因为它的内联函数全部被展开成了函数调用。到了-Os内联生效情况就反过来了。这就是为什么不同人测出来的绝对值和排名对不上。有人说 FreeRTOS 切换要 4 μs很可能他用的就是-O0或者根本没开优化。你在看任何性能数据之前第一件事是问清楚编译选项。还有一个隐蔽的坑内联函数里的 GPIO 翻转。如果你在测试任务里直接写GPIO_BOP(GPIOB) GPIO_PIN_0这行代码在-Os下可能被优化成一条位带写入指令在-O0下却是十几次内存访问。测量代码本身的开销在变你没测到 RTOS 的差异测到的是编译器的差异。4.2 默认配置里的隐藏差异每款 RTOS 出厂时都有一组默认配置而这些默认值的设计取向完全不同。举几个我实际遇到的FreeRTOS 的configUSE_PORT_OPTIMISED_TASK_SELECTION默认是 0也就是用 C 语言的CLZ指令算下一个任务改成 1 以后用硬件前导零指令切换能快 5%~8%。这个选项在很多移植教程里根本没提。RT-Thread 的RT_TICK_PER_SECOND默认 1000但它的软件定时器线程优先级默认设得比较高如果你的应用里用了大量定时器这个线程会频繁抢 CPU。μC/OS-III 默认开启了OS_CFG_STAT_TASK_EN也就是统计任务它会每隔一秒跑一次算 CPU 使用率这在测量时会造成周期性的干扰。关掉它切换时间能降 3% 左右。Zephyr 的CONFIG_SYS_CLOCK_TICKS_PER_SEC默认是 100比大多数 RTOS 低一个数量级。Tick 频率低意味着每次 Tick 处理的开销占比小但如果你的应用需要毫秒级的时间精度就得改到 1000改完之后各项数据都会变。4.3 测量链路自身的偏差这部分我在 2.2 里提过一部分再补充几个。逻辑分析仪的采样率不够高会出问题。100 MS/s 对应 10 ns 的时间分辨率测量 μs 级事件是够的但如果你用 1 MS/s 的入门级设备每个采样点间隔 1 μs测出来的数据基本就是随机数。我见过有人用这个精度的分析仪测出切换时间 0.5 μs那个 0.5 其实就是采样点间隔。GPIO 翻转的物理边沿也有上升时间。推挽输出加上几十皮法的负载电容边沿大约几纳秒对 μs 级测量可以忽略。但如果你接了很长的杜邦线边沿能拖到 100 ns 以上那就必须换短接线或者降低测量标准。还有一个更隐蔽的调试器的影响。SWD 调试器在连接状态下会让内核产生额外的总线活动如果开了实时变量查看或者 ITM 输出影响更大。我所有的性能数据都是拔掉调试器、上电复位后测的。4.4 面试题里流传的那些错误答案这段时间帮几个朋友看 RTOS 相关的面试题发现几处反复出现的错误说法顺便纠正一下。FreeRTOS 的任务切换时间是 1 μs因为 Cortex-M3 的 PendSV 只需要 12 个时钟周期。——12 个周期只是硬件入栈的时间不包含调度器选任务、寄存器出栈、返回跳转。全套走完在 72 MHz 下大约 1.6 μs。RTOS 的上下文切换比 Linux 快因为 Linux 要切换页表。——这个对比本身没什么意义。RTOS 切换快是因为任务数量少、没有虚拟内存、栈固定Linux 的切换开销大是因为页表、TLB、Cache 都要处理。两者服务于完全不同的场景不是同一条赛道。信号量一定比互斥量快。——在无竞争的情况下正好相反理由我在 3.2 里说过了。用 RTOS 就一定要开 Tick可以用 tickless 模式提升性能。——tickless 省的是功耗不是性能。开了 tickless 以后每次唤醒都要重新计算下一个到期时间某些实现里反而会增加平均开销。5. 排查实录数据不对劲时怎么定位5.1 现象与排查路径速查表测试过程中遇到的各种怪现象我整理成了一张表。这张表在真实项目里排查问题时也很好用。现象可能原因排查动作切换时间忽大忽小抖动超过 50%中断干扰、栈溢出边缘、编译器未内联关闭无关外设中断用 map 文件看栈余量检查优化等级测出来比理论值快一倍把往返时间当单次时间、GPIO 被优化掉检查测试任务的引脚翻转是否真的执行核对测量定义系统跑几分钟后死机栈溢出、堆碎片、优先级反转打开栈检测改用静态内存分配检查临界区时长中断里调用 API 后进入硬件错误优先级分组配置错误、用了非 FromISR 接口检查 NVIC 优先级分组替换成带 FromISR 后缀的接口移植后串口无输出时钟配置错误、rt_hw_console_output未实现用示波器直接量串口引脚先跑通裸机串口各任务运行时间不均衡时间片配置、优先级设置不合理打印各任务的执行次数重新划分优先级Flash 占用比预期大很多断言和调试代码未关闭、链接了完整 libc关闭调试宏改用 newlib-nano检查 map 文件里的意外符号唤醒延迟比切换时间大很多中断退出路径长、有钩子函数关闭内核钩子和对象统计检查 PendSV 优先级5.2 两个真实的翻车案例第一个案例发生在做 LiteOS-M 的时候。测出来的切换时间是 2.1 μs看起来正常但连续跑 2000 次以后偶尔会冒出一个 30 μs 的异常值。查了两天最后定位到是串口日志。我为了调试方便在调度器钩子里加了一行串口输出而这个钩子在每次切换时都会执行。串口输出一次要几十微秒正好对上。把日志挪到独立低优先级任务以后数据立刻就干净了。第二个案例更隐蔽。测 RT-Thread 完整版的时候信号量延迟总比预期高 1 μs 左右。我先怀疑是断言关掉以后没变化又怀疑是对象类型检查翻了源码发现确实有个字符串比较。但真正的原因是 FinSH 命令行线程的优先级设得比测试任务高它在后台等着串口输入每次系统节拍一到就去检查一次偶尔会插到测试任务前面。把 FinSH 线程优先级降到最低数据就正常了。这两个案例的共同点是干扰不来自被测对象来自你自己的调试代码和周边组件。所以我现在的测试规范是——先做一版纯净工程把所有非必要组件全部关掉测完基础数据再逐个开启看每开一个组件带来多少开销。这个方法论比单次测量值有用得多。6. 把快翻译成你自己的场景6.1 按场景选型别按排行榜选型看完数据你可能想问到底选哪个我的答案一向是看你干什么。如果你的产品是电池供电的传感器节点一次唤醒处理几十毫秒就睡那你该关心的是内存占用和 tickless 功耗ThreadX 和 RT-Thread Nano 在这个方向上很有优势因为它们的唤醒路径短、可以完全静态分配。如果你的产品是电机控制或者数字电源控制环频率在 10 kHz 以上那你该关心的是中断到任务的唤醒延迟和抖动。这种场景下我通常建议把控制环放在中断里或者用最高优先级任务切换速度反而不是瓶颈——真正决定控制质量的是那个最坏情况下的唤醒延迟不是平均值。如果你的项目要跑协议栈、文件系统、图形界面那 RT-Thread 完整版和 Zephyr 这类带完整组件生态的方案更合适。它们的内核开销大但省下来的集成时间远超那几微秒。我做过一个带 LCD 段码显示和 SD 卡日志存储的项目用 RT-Thread 完整版两周就搭起来了换成 FreeRTOS 加自研组件至少一个月。如果是教学或者准备面试FreeRTOS 和 RT-Thread Nano 源码量适中、结构清晰是入门的好选择。μC/OS-III 的代码注释极详尽读完对调度器设计的理解会上一个台阶。6.2 移植和维护成本这笔账要提前算性能数据好看不代表项目好做。我在选型的时候会额外算三笔账。第一笔是移植成本。在 GD32F103 这类兼容性好的芯片上八款 RTOS 的移植工作量差别不算大都是一天到三天。但如果换成一些小众芯片或者新的内核版本有没有现成端口就是天壤之别。FreeRTOS 和 Zephyr 的端口覆盖面最广μC/OS-III 稍窄其余几款在冷门芯片上的支持要具体查。第二笔是调试成本。有些 RTOS 的调试体验是断崖式的好比如 ThreadX 的 trace 组件可以输出完整的事件时间线Zephyr 的 logging 子系统可以远程抓日志RT-Thread 有 FinSH 可以在线查看所有内核对象状态。这些工具在小资源 MCU 上用不了但如果你用的是 128 KB 以上的片子它们能省下大量排查时间。第三笔是长期维护成本。选一个有活跃社区、有商业支持、有明确版本节奏的方案比选一个快 0.5 μs 的方案更重要。这一点在项目进入量产阶段以后体会最深——出了问题时能不能找到人、能不能找到答案比省那点 CPU 时间值钱得多。7. 这套测试方法还能怎么用7.1 把测试工程做成可复用的模板测完这一轮我把整个工程结构固化下来了分成三层底层是bench_port只做三件事配置系统时钟、初始化测量用的 GPIO、提供微秒级延时。这部分每换一块 MCU 就重写一次大概一百行代码。中间层是bench_rtos_adapter用一组函数指针把各家 RTOS 的 API 抽象成统一接口比如bench_task_create、bench_sem_pingpong、bench_queue_send。上层的测试用例只调用这组接口不直接碰任何 RTOS 头文件。这样换一个内核只需要重写这个适配层两百行左右。顶层是bench_case里面是各个测试用例每个用例自己负责翻转哪个引脚、跑多少次、怎么统计。所有结果通过串口按固定格式打印直接粘进表格就能出图。这套结构的好处是以后再有人跟我说某某 RTOS 特别快我可以半天之内跑出一份对照数据而不是重新搭一遍环境。7.2 下一步想扩展的方向目前的测试还有几个没覆盖到的点我准备后续补上。一是内存分配器的对比。这次所有测试都用静态分配因为动态分配会引入不确定性。但真实项目里动态分配用得很多heap_4、heap_5、RT-Thread 的 SLAB 分配器、TLSF 算法在不同分配模式下的性能差异很大尤其是碎片化以后的分配延迟。二是长时间运行下的稳定性。这次每个用例只跑了几千次属于短时测试。真正的考验是连续运行几个月看有没有计数溢出、有没有任务饿死、有没有内存缓慢增长。这个需要专门的测试板长期占用我打算用两块板子轮换着跑。三是与调试工具的配合。想知道在连接调试器、开启 trace、开启实时变量监控的情况下各款 RTOS 的性能衰减曲线。这个数据在真实开发过程中很有参考价值因为开发阶段几乎不可能拔掉调试器。最后一个提醒上面所有数据都是我在 GD32F103、AC6、-Os、72 MHz 这一组条件下的结果。你换一颗芯片、换一个编译器版本、把优化等级从-Os改成-O2绝对值会变某些项的排名也可能变。所以我从头到尾在强调方法而不是结论——把测量方法学明白比记住哪款快哪款慢有用得多。真正踩过几次坑以后你就会发现让 MCU 项目出问题的从来不是那 0.5 μs 的切换差距而是栈溢出、优先级反转和临界区过长这三件事。