本节摘要:RTOS 给了任务机制,但任务怎么分、模块怎么切、状态放哪里,是操作系统不会替你回答的设计题。本节给出"采集、处理、输出"三层划分法与三条依赖方向规则,讲清模块边界与状态归属的判断标准,并用一个从 400 行大 loop 重构为四任务的完整对照演示全过程。
4.1 节学会了驾驶单个任务,这一节回答车队怎么编队。它紧随内核机制,因为划分错误会在调度层面付出代价:本该并行的被串成一列,本该共享的被复制三份——任务机制放大了坏设计的破坏力,也放大了好设计的收益。
嵌入式系统有一条天然的河:数据从传感器进来,被加工,然后从执行器与显示出去。任务划分顺着这条河切三段——采集层只管取数入队,处理层只管算与决策,输出层只管把结果落盘、上屏、驱动执行器。每层任务各守一段河岸,层间只靠队列对话。
三层法的好处是节奏解耦:输出层写卡再慢,队列把波动挡在门外,采集层依然按 20 毫秒的节拍取数——4.2 节支柱页那张对照图说的就是这个结构。判据很简单:任何一层都不许替别的层等待。采集任务里出现刷屏调用、处理任务里出现写卡,都是越层违规。
三条依赖方向规则把边界钉死:其一,依赖只许顺流(采集的接口被处理调用,反之不许);其二,层间只通过队列与事件传递信息,不直接摸对方的全局变量;其三,公共的东西(配置、日志)做成独立模块沉底,谁都可以用,它们不许回头依赖任何层。

划分之后第一个争议永远是状态(变量)归属。规则只有一条:状态跟模块走,不跟任务走。传感器模块持有自己的原始读数缓存,滤波模块持有自己的历史窗口,任务函数本身几乎不持有状态——任务只是"按时唤醒某个模块干活"的调度壳。这样做的收益在测试:模块不依赖任务,就可以脱离调度器单独喂输入验输出;任务挪来挪去,模块纹丝不动。
反例是最常见的写法:任务函数里定义一堆局部静态变量保存中间结果,两周后另一个任务也要用这些中间结果——要么全局变量满天飞,要么把任务粘死在一起。看到 static 局部变量在任务函数里越积越多,就是状态该搬进模块的信号。
背景:一个数据记录器,全部逻辑挤在 loop 里:读三路传感器、软件滤波、OLED 刷新、按键菜单、SD 写入、LED 指示,约 400 行。症状是采样间隔在 20 毫秒到 400 毫秒间漂移——写卡的抖动全被采样吸走。
操作:第一步画数据流:传感器到滤波到存储与显示,标准三层。第二步切任务:采集任务 20 毫秒周期,取数入队;处理任务等队列,算滑动窗口滤波,结果入第二个队列;输出任务等第二队列,刷屏;存储任务攒批写卡;菜单任务只响应按键事件。第三步搬家:原有代码按模块拆进五个文件夹式的逻辑分组,static 状态全部随模块走。第四步钉死依赖:采集不许 include 显示,输出不许 include 采集,公共的配置与日志沉底。第五步调优:队列深度按"最坏一波产量"设为 16,写卡攒批 8 条一写。
结果:采样间隔稳定在 20 毫秒正负 1 毫秒;写卡高峰被队列完全吸收;新增一路传感器只动采集模块,其余四层零改动。
解读:对照重构前后,代码总行数几乎没变——架构的价值从来不是代码更少,而是变化的影响范围更小。重构前加传感器要通读 400 行找插入点;重构后改一个模块。衡量架构好坏的最朴素标尺,就是"改这里要不要懂全部"。
变式:小项目三层显得兴师动众时可以降级为"两任务加中断":采集走定时器中断,处理与输出合为一个任务——结构还在,只是层次少一层。原则不因项目小而打折:依赖方向与状态归属,两层时同样要守。
⚠️ 常见坑:为了"共享方便"把队列换成全局变量加标志位,是架构腐烂的第一步。标志位方案在两个写入者出现的那一刻就会出现竞态,而队列天生解决一切——不要用 4.1 节的锁去补救一个本该用队列的设计。
💡 关键直觉:画不出数据流向图的项目,任务怎么分都是错的。先把"数据从哪来、到哪去"画成箭头,任务边界自然浮现在箭头的断点上。
新项目开任务前,把这张清单走一遍,能挡住八成的架构返工:
评审通过的标志不是"都说没问题",而是每个数字(堆栈、队列深度、周期)都有出处。这份清单配上 4.1 节的水位查询,架构就从图纸变成了可执行的对账单。
三层划分落地时还有两个执行细节决定成败。重构节奏:别指望一次把大 loop 拆完——按"先采集、再输出、后处理"的顺序,每次只搬一层,搬完烧录验证功能等价,再动下一层。一次搬三层等于同时引入三个变量,出了问题连归因都无从下手。回退预案:每搬一层之前打上版本标记,功能异常时能一键回到上一层的状态;重构期的设备不进现场,现场版本与重构版本在分支上并行,直到重构版通过完整回归。
这两个细节背后是同一条经验:架构重构的风险不在设计对不对,而在迁移过程失控。设计对而迁移乱,会让团队对整个方案失去信心——所以把重构当成一串小交付来做,每一小步都可验证、可回退,重构就从冒险变成了例行工程。