FreeRTOS系列---队列详解

理解 FreeRTOS 的队列(Queue)实现需要从数据结构、操作接口和底层机制三个层面分析。队列是 FreeRTOS 中最重要的通信机制之一,支持任务间、任务与中断间的数据传递。以下结合源码(以 FreeRTOS v10.4.3 为例)详细讲解:


1. 队列的核心数据结构

源码位置:FreeRTOS/Source/include/queue.h

1.1 队列控制块 Queue_t
typedef struct QueueDefinition
{
    int8_t *pcHead;              // 队列存储区起始地址
    int8_t *pcWriteTo;           // 下一个写入位置
    int8_t *pcReadFrom;          // 下一个读取位置
    List_t xTasksWaitingToSend;  // 等待发送的任务列表(队列满时阻塞)
    List_t xTasksWaitingToReceive; // 等待接收的任务列表(队列空时阻塞)
    UBaseType_t uxMessagesWaiting; // 当前队列中的消息数量
    UBaseType_t uxLength;        // 队列最大容量(消息数量)
    UBaseType_t uxItemSize;      // 单个消息的字节大小
    volatile int8_t cRxLock;     // 接收锁(用于从队列读取时的互斥)
    volatile int8_t cTxLock;     // 发送锁(用于向队列写入时的互斥)
} Queue_t;
1.2 队列存储区
  • 队列的存储区是一个连续的字节数组,每个消息占用 uxItemSize 字节。
  • 示例:若队列长度为 5,每个消息占 4 字节,则存储区大小为 5 * 4 = 20 字节。

2. 队列的创建

源码位置:FreeRTOS/Source/queue.c

2.1 创建函数 xQueueCreate()
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize )
{
    Queue_t *pxNewQueue;
    size_t xQueueSizeInBytes = uxQueueLength * uxItemSize;
    
    // 分配内存:控制块 + 存储区
    pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
    
    if( pxNewQueue != NULL )
    {
        pxNewQueue->pcHead = ( int8_t * ) ( pxNewQueue + 1 ); // 存储区紧接控制块
        pxNewQueue->uxLength = uxQueueLength;
        pxNewQueue->uxItemSize = uxItemSize;
        pxNewQueue->pcWriteTo = pxNewQueue->pcHead;
        pxNewQueue->pcReadFrom = pxNewQueue->pcHead;
        vListInitialise( &( pxNewQueue->xTasksWaitingToSend ) );
        vListInitialise( &( pxNewQueue->xTasksWaitingToReceive ) );
    }
    return pxNewQueue;
}

3. 队列的发送与接收

3.1 发送操作 xQueueSend()

源码位置:xQueueGenericSend()

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void *pvItemToSend, TickType_t xTicksToWait, BaseType_t xCopyPosition )
{
    Queue_t * const pxQueue = xQueue;
    BaseType_t xEntryTimeSet = pdFALSE;
    
    // 如果队列已满且非阻塞,立即返回错误
    if( pxQueue->uxMessagesWaiting >= pxQueue->uxLength )
    {
        if( xTicksToWait == 0 )
            return errQUEUE_FULL;
        // 阻塞当前任务,加入等待发送列表
        vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
    }
    else
    {
        // 拷贝数据到队列
        prvCopyDataToQueue( pxQueue, pvItemToSend, xCopyPosition );
        // 如果有任务在等待接收,唤醒它
        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
            xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) );
        return pdPASS;
    }
}
3.2 数据拷贝函数 prvCopyDataToQueue()
static void prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
    if( pxQueue->uxItemSize > 0 )
    {
        // 计算写入位置
        int8_t *pcWriteTo = pxQueue->pcWriteTo;
        // 拷贝数据
        memcpy( pcWriteTo, pvItemToQueue, pxQueue->uxItemSize );
        // 更新写入指针(循环队列)
        pcWriteTo += pxQueue->uxItemSize;
        if( pcWriteTo >= pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ) )
            pcWriteTo = pxQueue->pcHead;
        pxQueue->pcWriteTo = pcWriteTo;
    }
    pxQueue->uxMessagesWaiting++;
}

4. 队列的阻塞机制

4.1 任务阻塞与唤醒
  • 发送阻塞:当队列满时,任务被挂起到 xTasksWaitingToSend 列表,并设置超时时间。
  • 接收阻塞:当队列空时,任务被挂起到 xTasksWaitingToReceive 列表。
  • 唤醒机制:当数据被写入或读取时,检查等待列表并唤醒优先级最高的任务。

5. 队列的应用场景

5.1 任务间通信
  • 生产者-消费者模型:一个任务生成数据(如传感器采样),另一个任务处理数据。
    // 生产者任务
    void vProducerTask( void *pvParameters )
    {
        SensorData_t xData;
        while(1)
        {
            xData = read_sensor();
            xQueueSend( xDataQueue, &xData, portMAX_DELAY );
        }
    }
    
    // 消费者任务
    void vConsumerTask( void *pvParameters )
    {
        SensorData_t xData;
        while(1)
        {
            xQueueReceive( xDataQueue, &xData, portMAX_DELAY );
            process_data(xData);
        }
    }
    
5.2 中断服务程序(ISR)与任务通信
  • 中断中发送数据:使用 xQueueSendFromISR()
    void ADC_IRQHandler( void )
    {
        uint16_t adc_value = read_adc();
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        xQueueSendFromISR( xADCFastQueue, &adc_value, &xHigherPriorityTaskWoken );
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
    
5.3 实现信号量(Semaphore)和互斥量(Mutex)
  • 二值信号量:队列长度为 1,消息大小为 0(仅触发信号)。
    // 创建二值信号量
    SemaphoreHandle_t xSemaphore = xQueueCreate( 1, 0 );
    
    // 释放信号量
    xQueueSend( xSemaphore, NULL, 0 );
    
    // 获取信号量
    xQueueReceive( xSemaphore, NULL, portMAX_DELAY );
    

6. 队列的高级特性

6.1 覆盖写入(Overwrite)
  • 当队列满时,覆盖最旧的数据(适用于传输最新状态)。
    xQueueOverwrite( xQueue, pvItemToSend );
    
6.2 优先级继承(Mutex 场景)
  • 互斥量(Mutex)基于队列实现,支持优先级继承,防止优先级反转。
    xSemaphoreCreateMutex(); // 内部调用 xQueueCreateMutex()
    

7. 源码级调试技巧

  1. 观察队列控制块
    使用调试器查看 uxMessagesWaitingpcWriteTopcReadFrom 的值。
  2. 跟踪阻塞列表
    检查 xTasksWaitingToSendxTasksWaitingToReceive 中的任务句柄。
  3. 分析内存布局
    队列存储区位于控制块之后,可通过 pcHead 指针访问原始数据。

8. 性能优化建议

  • 静态内存分配:使用 xQueueCreateStatic() 避免动态内存分配。
  • 零拷贝技术:对于大型数据,传递指针而非数据本身(需自行管理内存生命周期)。
  • 中断优化:在 ISR 中始终使用 FromISR 后缀的 API,避免不必要的上下文切换。

总结

FreeRTOS 的队列实现核心是 循环缓冲区 + 任务阻塞列表

  1. 数据结构:循环存储区 + 双阻塞列表。
  2. 操作原子性:通过关闭中断或调度器锁保证操作安全。
  3. 应用场景:任务间通信、中断通信、信号量/互斥量实现。

队列的设计体现了 RTOS 的典型特征:高效的数据传递、确定性的阻塞唤醒机制、对中断上下文的特殊处理。理解其源码实现有助于在资源受限的嵌入式系统中合理使用队列,并解决复杂的同步问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值