RTOS之环形缓冲区和队列

一、环形缓冲区(Circular Buffer)

类似一个环形跑道,运动员(数据)在跑道上循环奔跑。跑道首尾相连,运动员跑到终点后又会回到起点继续跑。

实际上环形缓冲区是一个固定大小的连续内存空间,用两个指针管理数据:

  • 写指针:指向下一个可以写入数据的位置。
  • 读指针:指向下一个可以读取的数据位置。

当数据写到缓冲区末尾时,会自动回到开头继续写(类似“循环”),覆盖旧数据或阻止写入(取决于设计)。

运行机制

关键设计:牺牲一个存储单元

环形缓冲区为了明确区分“满”和“空”两种状态,通常会牺牲一个存储单元(即实际可用大小为 N-1,假设总容量为 N)。

状态判断逻辑

可写入状态:写指针以及写指针的下一个位置不等于读指针。

空状态:读指针和写指针指向同一位置(read_ptr == write_ptr)。

满状态:写指针的下一个位置(考虑回绕)等于读指针((write_ptr + 1) % N == read_ptr)。

示例说明

假设缓冲区总容量 N=5,实际可用大小为 4。

  • 可写入状态:read_ptr = 2,write_ptr = 4,那么读指针可以回到位置1进行写入操作。
  • 空状态:read_ptr = 0,write_ptr = 0 → 无数据。
  • 满状态:read_ptr = 0, write_ptr = 4 → 下一个写入位置是(4+1)%5=0,与读指针重合。

优点

1.高效读写

  • 无需频繁分配/释放内存,读写直接通过指针移动完成,时间复杂度为 O(1)。
  • 适合实时系统。

2.内存利用率高

  • 固定大小,内存预分配,避免内存碎片。
  • 数据覆盖机制可重复利用空间

3.避免数据搬迁

  • 普通队列在出队后需移动数据,而环形缓冲区只需移动指针。

缺点

1.固定容量

  • 缓冲区大小需预先确定,扩容需重建整个缓冲区(复杂且耗时)。

2.数据覆盖风险

  • 写入速度过快时,未读取的旧数据可能被覆盖(需设计是否允许覆盖)。

3.无法随机访问历史数据

  • 只能按顺序读取,若需访问旧数据需额外设计(如日志缓存不适用)。

二、队列

FreeRTOS 中的队列(Queue)基于环形缓冲区的设计,增加了任务调度、阻塞机制和同步控制。

队列的结构

1.环形缓冲区(核心数据存储)
作用:实际存储队列中的数据,通过指针回绕实现循环读写。

2.任务阻塞链表(同步机制的核心)
FreeRTOS 队列通过两个链表管理因队列状态(满或空)而阻塞的任务:

  • xTasksWaitingToSend:
    作用:当队列已满时,尝试写入数据的任务会被挂起并加入此链表。
    唤醒条件:队列中有空闲位置(消费者读取数据后)。

  • xTasksWaitingToReceive:
    作用:当队列为空时,尝试读取数据的任务会被挂起并加入此链表。
    唤醒条件:队列中有新数据写入(生产者写入数据后)。

3.其他

  • uxMessagesWaiting(消息计数器):
    记录当前队列中有效数据的数量,用于快速判断队列的空/满状态。
    替代指针比较:无需通过 pcReadFrom 和 pcWriteTo 的相对位置判断队列状态,直接通过计数器即可(简化逻辑)。
  • 互斥锁(uxRxLock 和 uxTxLock):
    作用:在中断服务程序(ISR)中操作队列时,通过锁机制保证原子性。

FreeRTOS 队列的结构体定义(简化版):

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;
}

RTOS之环形缓冲区和队列_第1张图片

写队列

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;
}

你可能感兴趣的:(RTOS,单片机,stm32,嵌入式硬件,c语言)