Skip to content

通用 RingBuffer —— 第一版

连续到来的字节,生产速度和消费速度不完全一致时,数据到底怎么暂存?

1. 问题与背景

RingBuffer 就是在解决:生产者和消费者速度不完全同步的问题 最核心就两个位置:head 和 tail head → 下一个数据应该写到哪里 tail → 下一个数据应该从哪里读

例如: Index: 0 1 2 3 4 5 6 7 ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ A │ B │ C │ D │ E │ F │ G │ H │ └───┴───┴───┴───┴───┴───┴───┴───┘ ↑ ↑ tail head

意味着:tail = 0 head = 3

下一次 Read → buffer[0] → A 下一次 Write → buffer[3]

代码里怎么实现 回绕? 最直观: head++; if (head >= capacity) { head = 0; }

也可以:head = (head + 1U) % capacity;

后一种更短,不过以后高频路径我们还会讨论:% 和2的幂容量优化。

怎么区分 Empty 和 Full? 方法 1:额外保存 count head; tail; count; count == 0 → Empty count == capacity → Full 虽然很直观,但是后面 ISR + Task 并发的时候,count 会成为一个麻烦: Producer修改 count Consumer也修改 count 两边共同写同一个变量,我们以后会分析 Race。

方法 2:额外保存 full Flag bool full可以解决但是多了分共享状态

方法 3:故意空出一个 Slot 也是我们采用的方法 规定: head == tail → 一定表示 Empty head下一步如果撞上tail → 认为 Full

因此容量为:N 实际最多存:N - 1个元素 它有个很好的点就是Producer 主要修改 head Consumer主要修改 tail 做: ISR Producer Task Consumer 时,这个设计特别值得研究。

虽然现在还不能简单宣布它线程安全,但数据所有权已经更干净。 我们从未来并发模型反推数据结构。

2. 必要基础

3. 底层原理

4. 数据流与调用流程

5. 最小验证实验

6. 调试观察

7. 常见错误与根因

8. 真实工程中的用途

9. 我的结论

FreeRTOS 内核与工程实践