什么是环形缓冲区
环形缓冲区,也称为循环缓冲区或环形队列,是一种特殊的FIFO(先进先出)数据结构。它使用一块固定大小的内存空间来缓存数据,并通过两个指针(读指针和写指针)来管理数据的读写。当任意一个指针到达缓冲区末尾时,会自动回绕到缓冲区开头,形成一个"环"。
环形缓冲区的用途
- 串口通信
在嵌入式设备中,串口是常用的通信接口。环形缓冲区可用于缓存收发数据,平衡通信速率差异。 - 音视频数据处理
音视频数据往往是连续的数据流。使用环形缓冲区可以平滑数据的生成和消耗,避免数据丢失或延迟。 - 传感器数据采集
传感器数据通常以固定频率采样。环形缓冲区可作为数据采集和处理之间的缓冲,降低实时性要求。 - 多线程数据传递
在多线程编程中,环形缓冲区是一种简单高效的线程间通信方式,无需复杂的同步操作。 - 数据打包与解析
一些通信协议使用特定的数据帧格式。环形缓冲区可用于数据的打包和解析,保证数据的完整性。
环形缓冲区的实现示例
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 环形缓冲区大小
#define BUFFER_SIZE 256
// 定义解析器状态
typedef enum {
STATE_WAIT_START, // 等待消息开始
STATE_READ_LENGTH, // 读取消息长度
STATE_READ_DATA // 读取消息数据
} ParserState;
typedef struct {
uint8_t buffer[BUFFER_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
} RingBuffer;
void RingBuffer_Init(RingBuffer *rb) {
rb->head = 0;
rb->tail = 0;
}
bool RingBuffer_Write(RingBuffer *rb, uint8_t data) {
uint16_t next = (rb->head + 1) % BUFFER_SIZE;
if (next == rb->tail) {
// 缓冲区满
return false;
}
rb->buffer[rb->head] = data;
rb->head = next;
return true;
}
bool RingBuffer_Read(RingBuffer *rb, uint8_t *data) {
if (rb->head == rb->tail) {
// 判满
return false;
}
*data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % BUFFER_SIZE;
return true;
}
// 处理完整消息的函数
void process_message(const uint8_t *msg, uint8_t length) {
for (uint8_t i = 0; i < length; i++) {
printf("%c", msg[i]);
}
printf("\n");
}
// 有限状态机解析器
void parse_messages(RingBuffer *rb) {
static ParserState state = STATE_WAIT_START;
static uint8_t msg_length = 0;
static uint8_t msg_index = 0;
static uint8_t message[128]; //
uint8_t byte;
while (RingBuffer_Read(rb, &byte)) {
switch (state) {
case STATE_WAIT_START: // 等待消息起始
if (byte == 0xAA) { // 0xAA是消息起始标志
state = STATE_READ_LENGTH;
}
break;
case STATE_READ_LENGTH: // 读取消息长度
msg_length = byte;
if (msg_length > 0 && msg_length < sizeof(message)) {
msg_index = 0;
state = STATE_READ_DATA;
} else {
// 无效长度,重置状态
state = STATE_WAIT_START;
}
break;
case STATE_READ_DATA: // 读取消息数据
message[msg_index++] = byte;
if (msg_index >= msg_length) {
process_message(message, msg_length);
state = STATE_WAIT_START;
}
break;
default:
state = STATE_WAIT_START;
break;
}
}
}
// 发送函数
void threadA_send(RingBuffer *rb, const uint8_t *msg, uint8_t length) {
RingBuffer_Write(rb, 0xAA); // 起始标志
RingBuffer_Write(rb, length); // 长度字段
for (uint8_t i = 0; i < length; i++) {
RingBuffer_Write(rb, msg[i]);
}
}
// 接收函数
void threadB_receive(RingBuffer *rb) {
parse_messages(rb);
}
void test_ring_buffer(void) {
// 1. 初始化环形缓冲区
RingBuffer rb;
RingBuffer_Init(&rb);
printf("=== 环形缓冲区测试开始 ===\n\n");
// 2. 测试基本消息发送和接收
printf("测试1: 基本消息收发\n");
const uint8_t test_msg1[] = "Hello World";
threadA_send(&rb, test_msg1, sizeof(test_msg1) - 1);
threadB_receive(&rb);
// 3. 测试空缓冲区
printf("\n测试2: 空缓冲区读取\n");
uint8_t temp;
if (!RingBuffer_Read(&rb, &temp)) {
printf("空缓冲区测试通过: 无法从空缓冲区读取数据\n");
}
// 4. 测试缓冲区满状态
printf("\n测试3: 缓冲区满状态\n");
uint8_t large_msg[BUFFER_SIZE];
for (int i = 0; i < BUFFER_SIZE; i++) {
large_msg[i] = 'A' + (i % 26); // 填充A-Z循环
}
bool write_result = true;
int write_count = 0;
while (write_result && write_count < BUFFER_SIZE + 10) {
write_result = RingBuffer_Write(&rb, large_msg[write_count % BUFFER_SIZE]);
write_count++;
}
printf("写入计数: %d (应小于缓冲区大小)\n", write_count - 1);
// 5. 测试长消息分段发送
printf("\n测试4: 长消息分段发送\n");
RingBuffer_Init(&rb); // 重新初始化
const uint8_t long_msg[] = "This is a long message to test multiple segments";
const int SEGMENT_SIZE = 10;
for (size_t i = 0; i < (size_t)(sizeof(long_msg) - 1); i += (size_t)SEGMENT_SIZE) {
size_t current_length = ((sizeof(long_msg) - 1 - i) < (size_t)SEGMENT_SIZE) ?
(sizeof(long_msg) - 1 - i) : (size_t)SEGMENT_SIZE;
threadA_send(&rb, &long_msg[i], current_length);
threadB_receive(&rb);
}
// 6. 测试无效消息
printf("\n测试5: 无效消息处理\n");
uint8_t invalid_msg[] = {0xAA, 0xFF, 0x01, 0x02}; // 无效长度
for (size_t i = 0; i < sizeof(invalid_msg); i++) {
RingBuffer_Write(&rb, invalid_msg[i]);
}
threadB_receive(&rb);
// 7. 测试快速读写切换
printf("\n测试6: 快速读写切换\n");
const uint8_t test_msg2[] = "Test";
for (int i = 0; i < 5; i++) {
threadA_send(&rb, test_msg2, sizeof(test_msg2) - 1);
threadB_receive(&rb);
}
printf("\n=== 环形缓冲区测试完成 ===\n");
}
int main() {
test_ring_buffer();
return 0;
}
总结
与传统的数组或链表相比,环形缓冲区有以下优点:
1.无需频繁移动数据。环形缓冲区的读写指针移动不会导致数据搬移,效率更高。
2.自动处理缓冲区"满"和"空"的状态。通过读写指针的关系可以判断缓冲区状态,无需额外的计数器。
- 适用于生产者-消费者模型。一个线程写入数据,另一个线程读取数据,天然支持异步处理。