microbus 核心实现:分发快照让回调再入队也不失控

声明: microbus 是一个开源项目,作者为 Peter Vanderhell。本文是阅读该项目源码和文档后整理的学习笔记,用于理解嵌入式事件总线在裸机上的确定性实现方式。本文作者不是该项目的开发者,未参与该项目的任何代码贡献。 文中所有工程细节均来自对开源代码的分析,不代表本文作者的设计决策。

项目仓库:github.com/Vanderhell/microbus

这篇文章讲 microbus 源码里的三个实现思想:状态归调用者、分发确定性优先、队列用快照保证有界。不重复入门用法,直接看机制。

核心思想

microbus 的全部设计围绕一个目标:在单上下文裸机上提供确定性的发布订阅。三个落点:

订阅表与投递期锁定

订阅表就是 mbus_t 里的 subs[16] 数组,每个槽是 {topic, handler, ctx, active}。分发时 mbus_deliver 从下标 0 升序扫到 MBUS_MAX_SUBSCRIBERS,话题匹配或命中 MBUS_TOPIC_ANY 就调用回调。

确定性从哪来:mbus_deliver 进出时维护 delivery_depth 计数。深度大于 0 期间,subscribeunsubscribeunsubscribe_topic 直接返回 MBUS_ERR_BUSY。效果是分发期间订阅表不可变,遍历顺序和结果都可预测。代价是回调里不能改订阅,只能等分发结束再改。

订阅表与投递期锁定

环形队列的单调计数

队列是 queue[8]q_headq_tail,两个 32 位计数器只增不减,用 (head & (DEPTH-1)) 取模定位槽位,所以深度必须是 2 的幂。满判定是 head - tail >= DEPTH,空判定是 head == tail

单调计数的好处:不需要单独的 count 字段;32 位计数要入队 2^32 次才回绕;单生产者单消费者下 head 和 tail 各自只被一方写,配合临界区宏就能构成 ISR 与主循环的通道。

dispatch 入口快照

队列存在的意义是解耦产生事件和处理事件。mbus_dispatch 的实现思想是快照:

  1. 入口取 eligible = 当前队列计数
  2. 循环最多处理 eligible 个:pop 到局部事件,分发,eligible--
  3. 回调里再入队的事件本回合不处理,留到下次 dispatch

没有快照的版本,回调再入队会让循环追着队尾跑,可能无限执行。快照把单次 dispatch 的执行时间变成有界:最多 eligible 次回调,回调里入队再多也只增长 head。

dispatch 入口快照

ABI 校验与统计计数

设计取舍与边界