类似一个环形跑道,运动员(数据)在跑道上循环奔跑。跑道首尾相连,运动员跑到终点后又会回到起点继续跑。
实际上环形缓冲区是一个固定大小的连续内存空间,用两个指针管理数据:
当数据写到缓冲区末尾时,会自动回到开头继续写(类似“循环”),覆盖旧数据或阻止写入(取决于设计)。
环形缓冲区为了明确区分“满”和“空”两种状态,通常会牺牲一个存储单元(即实际可用大小为 N-1,假设总容量为 N)。
可写入状态:写指针以及写指针的下一个位置不等于读指针。
空状态:读指针和写指针指向同一位置(read_ptr == write_ptr)。
满状态:写指针的下一个位置(考虑回绕)等于读指针((write_ptr + 1) % N == read_ptr)。
示例说明
假设缓冲区总容量 N=5,实际可用大小为 4。
1.高效读写
2.内存利用率高
3.避免数据搬迁
1.固定容量
2.数据覆盖风险
3.无法随机访问历史数据
FreeRTOS 中的队列(Queue)基于环形缓冲区的设计,增加了任务调度、阻塞机制和同步控制。
1.环形缓冲区(核心数据存储)
作用:实际存储队列中的数据,通过指针回绕实现循环读写。
2.任务阻塞链表(同步机制的核心)
FreeRTOS 队列通过两个链表管理因队列状态(满或空)而阻塞的任务:
xTasksWaitingToSend:
作用:当队列已满时,尝试写入数据的任务会被挂起并加入此链表。
唤醒条件:队列中有空闲位置(消费者读取数据后)。
xTasksWaitingToReceive:
作用:当队列为空时,尝试读取数据的任务会被挂起并加入此链表。
唤醒条件:队列中有新数据写入(生产者写入数据后)。
3.其他
typedef struct QueueDefinition {
int8_t *pcHead; // 队列缓冲区起始地址
int8_t *pcTail; // 缓冲区结束地址的下一个字节
int8_t *pcWriteTo; // 下一个可写入位置(写指针)
int8_t *pcReadFrom; // 下一个可读取位置(读指针)
UBaseType_t uxMessagesWaiting; // 当前队列中数据项的数量
UBaseType_t uxLength; // 队列容量(最大数据项数)
UBaseType_t uxItemSize; // 每个数据项的大小(字节)
List_t xTasksWaitingToSend; // 等待发送的任务列表(队列满时阻塞)
List_t xTasksWaitingToReceive; // 等待接收的任务列表(队列空时阻塞)
volatile UBaseType_t uxRxLock; // 接收锁(用于中断安全操作)
volatile UBaseType_t uxTxLock; // 发送锁(用于中断安全操作)
} Queue_t;
队列初始化时分配一块连续内存作为缓冲区,并设置指针和容量:
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize) {
// 计算总缓冲区大小:容量 * 数据项大小 + 头尾标记
size_t xBufferSize = uxQueueLength * uxItemSize;
Queue_t *pxQueue = pvPortMalloc(sizeof(Queue_t) + xBufferSize);
// 初始化环形缓冲区指针
pxQueue->pcHead = (int8_t *)(pxQueue + 1); // 缓冲区起始位置
pxQueue->pcTail = pxQueue->pcHead + xBufferSize;
pxQueue->pcWriteTo = pxQueue->pcHead;
pxQueue->pcReadFrom = pxQueue->pcHead;
pxQueue->uxLength = uxQueueLength;
pxQueue->uxItemSize = uxItemSize;
pxQueue->uxMessagesWaiting = 0;
// 初始化任务阻塞列表
vListInitialise(&pxQueue->xTasksWaitingToSend);
vListInitialise(&pxQueue->xTasksWaitingToReceive);
return pxQueue;
}
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToSend, TickType_t xTicksToWait) {
Queue_t *pxQueue = (Queue_t *)xQueue;
// 检查队列是否已满
//pxQueue->uxMessagesWaiting:当前队列中已存储的数据项数量
if (pxQueue->uxMessagesWaiting >= pxQueue->uxLength) {
if (xTicksToWait == 0) {
return errQUEUE_FULL; // 非阻塞模式,直接返回错误
}
// 阻塞当前任务,加入等待发送列表
vTaskPlaceOnEventList(&pxQueue->xTasksWaitingToSend, xTicksToWait);
taskYIELD(); // 触发任务调度
}
// 写入数据到 pcWriteTo 指向的位置
memcpy(pxQueue->pcWriteTo, pvItemToSend, pxQueue->uxItemSize);
// 更新写指针(环形回绕)
pxQueue->pcWriteTo += pxQueue->uxItemSize;
//pcTail指向索引5的位置,即缓冲区外的下一个字节
//检查写指针是否已经到达或超过了缓冲区的物理末尾
if (pxQueue->pcWriteTo >= pxQueue->pcTail) {
pxQueue->pcWriteTo = pxQueue->pcHead;
}
pxQueue->uxMessagesWaiting++;
// 如果有任务在等待接收,唤醒最高优先级任务
if (listLIST_IS_EMPTY(&pxQueue->xTasksWaitingToReceive) == pdFALSE) {
xTaskNotifyFromISR(xTaskGetCurrentTaskHandle(), ...);
}
return pdPASS;
}
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait) {
Queue_t *pxQueue = (Queue_t *)xQueue;
// 检查队列是否为空
if (pxQueue->uxMessagesWaiting == 0) {
if (xTicksToWait == 0) {
return errQUEUE_EMPTY; // 非阻塞模式,直接返回错误
}
// 阻塞当前任务,加入等待接收列表
vTaskPlaceOnEventList(&pxQueue->xTasksWaitingToReceive, xTicksToWait);
taskYIELD(); // 触发任务调度
}
// 从 pcReadFrom 指向的位置读取数据
memcpy(pvBuffer, pxQueue->pcReadFrom, pxQueue->uxItemSize);
// 更新读指针(环形回绕)
pxQueue->pcReadFrom += pxQueue->uxItemSize;
if (pxQueue->pcReadFrom >= pxQueue->pcTail) {
pxQueue->pcReadFrom = pxQueue->pcHead;
}
pxQueue->uxMessagesWaiting--;
// 如果有任务在等待发送,唤醒最高优先级任务
if (listLIST_IS_EMPTY(&pxQueue->xTasksWaitingToSend) == pdFALSE) {
xTaskNotifyFromISR(xTaskGetCurrentTaskHandle(), ...);
}
return pdPASS;
}
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
// 定义队列参数
#define QUEUE_LENGTH 5 // 队列容量(最多存放 5 个数据项)
#define ITEM_SIZE sizeof(int) // 每个数据项的大小(此处以 int 为例)
// 全局队列句柄
QueueHandle_t xQueue;
// 生产者任务(写队列)
void vProducerTask(void *pvParameters) {
int value = 0;
while (1) {
// 向队列发送数据(阻塞时间为 0,队列满时立即返回错误)
if (xQueueSend(xQueue, &value, 0) == pdPASS) {
printf("Produced: %d\n", value);
value++;
} else {
printf("Queue full! Retrying...\n");
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟 1 秒
}
}
// 消费者任务(读队列)
void vConsumerTask(void *pvParameters) {
int received_value;
while (1) {
// 从队列接收数据(阻塞时间 portMAX_DELAY 表示无限等待直到有数据)
if (xQueueReceive(xQueue, &received_value, portMAX_DELAY) == pdPASS) {
printf("Consumed: %d\n", received_value);
}
}
}
int main() {
// 初始化队列
xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
if (xQueue == NULL) {
printf("Queue creation failed!\n");
return -1;
}
// 创建生产者和消费者任务
xTaskCreate(vProducerTask, "Producer", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreate(vConsumerTask, "Consumer", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 若调度器启动失败,执行到此
while (1);
return 0;
}