理解 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. 源码级调试技巧
- 观察队列控制块:
使用调试器查看uxMessagesWaiting
、pcWriteTo
和pcReadFrom
的值。 - 跟踪阻塞列表:
检查xTasksWaitingToSend
和xTasksWaitingToReceive
中的任务句柄。 - 分析内存布局:
队列存储区位于控制块之后,可通过pcHead
指针访问原始数据。
8. 性能优化建议
- 静态内存分配:使用
xQueueCreateStatic()
避免动态内存分配。 - 零拷贝技术:对于大型数据,传递指针而非数据本身(需自行管理内存生命周期)。
- 中断优化:在 ISR 中始终使用
FromISR
后缀的 API,避免不必要的上下文切换。
总结
FreeRTOS 的队列实现核心是 循环缓冲区 + 任务阻塞列表:
- 数据结构:循环存储区 + 双阻塞列表。
- 操作原子性:通过关闭中断或调度器锁保证操作安全。
- 应用场景:任务间通信、中断通信、信号量/互斥量实现。
队列的设计体现了 RTOS 的典型特征:高效的数据传递、确定性的阻塞唤醒机制、对中断上下文的特殊处理。理解其源码实现有助于在资源受限的嵌入式系统中合理使用队列,并解决复杂的同步问题。