1. 消息队列API
FreeRTOS 的消息队列是任务间通信(IPC)的核心机制,它不仅仅是简单的数据传递通道,更是实现信号量、互斥量等同步原语的基础,消息队列本文分为两篇,本篇是下篇重点介绍消息队列的内部机制,在阅读本文前建议先看上篇文章搞懂消息队列,你的 FreeRTOS 才算入门,由浅入深进行阅读
FreeRTOS 队列的设计遵循以下核心原则:
按值复制:数据在入队时被完整复制到队列内部缓冲区,而非传递指针。这避免了悬空指针和数据竞争问题。 FIFO 顺序:默认先进先出,同时支持优先级消息(发送到队首)。 阻塞与超时:支持任务在队列满/空时阻塞等待,带超时机制。 ISR 安全:提供独立的 FromISRAPI,可在中断中安全操作。
1.1 API 层次结构
FreeRTOS 队列 API 采用分层设计:

应用层:用户直接调用的宏( xQueueCreate, xQueueSend, xQueueReceive)通用层:实际实现逻辑的函数( xQueueGenericSend, xQueueGenericCreate)辅助层:内部数据操作函数( prvCopyDataToQueue, prvCopyDataFromQueue)任务层:任务阻塞/唤醒管理( vTaskPlaceOnEventList, xTaskRemoveFromEventList)移植层:临界区和上下文切换( taskENTER_CRITICAL, portYIELD)
2. 核心数据结构 Queue_t
队列的核心是 Queue_t 结构体(在 queue.c 中定义为 xQUEUE),它包含了队列运行所需的所有状态信息。

2.1 结构体定义
typedef struct QueueDefinition{int8_t *pcHead; // 指向队列存储区起始位置int8_t *pcWriteTo; // 指向下一次写入的位置union{QueuePointers_t xQueue; // 队列模式使用的指针SemaphoreData_t xSemaphore; // 信号量模式使用的数据} u;List_t xTasksWaitingToSend; // 等待发送的任务列表(按优先级排序)List_t xTasksWaitingToReceive; // 等待接收的任务列表(按优先级排序)volatile UBaseType_t uxMessagesWaiting; // 当前队列中的消息数量UBaseType_t uxLength; // 队列容量(最大消息数)UBaseType_t uxItemSize; // 每条消息的字节大小volatile int8_t cRxLock; // 接收锁计数器volatile int8_t cTxLock; // 发送锁计数器} xQUEUE;typedef xQUEUE Queue_t;
2.2 关键字段解析
指针字段(队列模式)
pcHead | Queue_t 结构体之后的内存 |
pcWriteTo | pcHead |
u.xQueue.pcTail | pcHead + uxLength * uxItemSize) |
u.xQueue.pcReadFrom | pcHead |
计数字段
uxMessagesWaiting | |
uxLength | |
uxItemSize |
等待列表
xTasksWaitingToSend | |
xTasksWaitingToReceive |
两个列表都按任务优先级排序,确保高优先级任务优先被唤醒。
锁计数器
cRxLock | -1 表示解锁,>=0 表示锁定期间接收的消息数 |
cTxLock | -1 表示解锁,>=0 表示锁定期间发送的消息数 |
2.3 联合体设计
Queue_t 使用联合体来区分队列和信号量两种模式:
union{QueuePointers_t xQueue; // 队列模式:存储读写指针SemaphoreData_t xSemaphore; // 信号量模式:存储互斥量持有者} u;
队列模式:使用 pcTail和 pcReadFrom实现环形缓冲区信号量模式:使用 xMutexHolder记录互斥量持有者任务句柄
这种设计使得同一个结构体可以同时支持队列和信号量,减少了代码重复。
3. 环形缓冲区原理
FreeRTOS 队列使用环形缓冲区(Circular Buffer)存储消息数据,这是一种高效的数据结构,支持 O(1) 时间复杂度的入队和出队操作。

3.1 内存布局
队列的存储区是一块连续的内存,逻辑上视为环形
pcHead:存储区起始,固定不变 pcTail:存储区结束,pcHead + uxLength * uxItemSize pcWriteTo:下一个写入位置,入队后前进 pcReadFrom:上次读取位置,出队后前进
3.2 入队操作(写入)
prvCopyDataToQueue() 函数负责将数据复制到队列:
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,const void *pvItemToQueue,const BaseType_t xPosition ){if( xPosition == queueSEND_TO_BACK ){// 复制数据到 pcWriteTo 位置memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue,( size_t ) pxQueue->uxItemSize );// pcWriteTo 前进pxQueue->pcWriteTo += pxQueue->uxItemSize;// 到达尾部则回绕到头部if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail ){pxQueue->pcWriteTo = pxQueue->pcHead;}}else // queueSEND_TO_FRONT{// 复制数据到 pcReadFrom 位置memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue,( size_t ) pxQueue->uxItemSize );// pcReadFrom 后退(注意方向相反)pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;// 到达头部则回绕到尾部if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ){pxQueue->u.xQueue.pcReadFrom =( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );}}// 消息计数增加pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;return xReturn;}
关键点:
发送到队尾:从 pcWriteTo写入,指针前进发送到队首:从 pcReadFrom写入,指针后退指针到达边界时自动回绕,实现环形效果
3.3 出队操作(读取)
prvCopyDataFromQueue() 函数负责从队列读取数据:
staticvoidprvCopyDataFromQueue( Queue_t * const pxQueue,void * const pvBuffer ){if( pxQueue->uxItemSize != 0 ){// pcReadFrom 前进pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize;// 到达尾部则回绕到头部if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail ){pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;}// 从 pcReadFrom 位置复制数据memcpy( ( void * ) pvBuffer,( void * ) pxQueue->u.xQueue.pcReadFrom,( size_t ) pxQueue->uxItemSize );}}
重要细节:pcReadFrom 是先前进再读取,这意味着它指向的是上次读取的位置,而非下一次读取的位置。
3.4 队列重置
xQueueGenericReset() 函数将队列恢复到初始状态:
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue ){pxQueue->u.xQueue.pcTail = pxQueue->pcHead +( pxQueue->uxLength * pxQueue->uxItemSize );pxQueue->uxMessagesWaiting = 0;pxQueue->pcWriteTo = pxQueue->pcHead;pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead +( ( pxQueue->uxLength - 1 ) * pxQueue->uxItemSize );pxQueue->cRxLock = queueUNLOCKED;pxQueue->cTxLock = queueUNLOCKED;// ...}
注意 pcReadFrom 初始化为最后一个槽位,因为它是"先前进再读取"的设计。
4. 队列创建过程

4.1 动态创建
xQueueGenericCreate() 函数负责动态分配内存并初始化队列:
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,const uint8_t ucQueueType ){Queue_t *pxNewQueue;size_t xQueueSizeInBytes;uint8_t *pucQueueStorage;// 计算存储区大小xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );// 一次性分配 Queue_t 结构体 + 存储区pxNewQueue = ( Queue_t * ) pvPortMalloc(sizeof( Queue_t ) + xQueueSizeInBytes );if( pxNewQueue != NULL ){// 存储区紧随 Queue_t 之后pucQueueStorage = ( uint8_t * ) pxNewQueue;pucQueueStorage += sizeof( Queue_t );// 初始化队列prvInitialiseNewQueue( uxQueueLength, uxItemSize,pucQueueStorage, ucQueueType, pxNewQueue );}return pxNewQueue;}
内存分配策略:

这种"一次分配"策略减少了内存碎片和分配开销。
4.2 静态创建
xQueueGenericCreateStatic() 使用用户提供的内存:
QueueHandle_t xQueueGenericCreateStatic(const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,uint8_t *pucQueueStorage,StaticQueue_t *pxStaticQueue,const uint8_t ucQueueType ){Queue_t *pxNewQueue; // 使用用户提供的结构体pxNewQueue = ( Queue_t * ) pxStaticQueue;if( pxNewQueue != NULL ){// 标记为静态分配(删除时不会 free)pxNewQueue->ucStaticallyAllocated = pdTRUE;prvInitialiseNewQueue( uxQueueLength, uxItemSize,pucQueueStorage, ucQueueType, pxNewQueue );}return pxNewQueue;}
4.3 初始化过程
prvInitialiseNewQueue() 完成队列的初始化:
staticvoidprvInitialiseNewQueue( const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,uint8_t *pucQueueStorage,const uint8_t ucQueueType,Queue_t *pxNewQueue ){// 信号量模式下,pcHead 不能为 NULL(NULL 表示互斥量)if( uxItemSize == 0 ){pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;}else{pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;}pxNewQueue->uxLength = uxQueueLength;pxNewQueue->uxItemSize = uxItemSize;// 调用 Reset 完成剩余初始化xQueueGenericReset( pxNewQueue, pdTRUE );}
5. 消息发送机制
xQueueGenericSend() 是所有发送操作的核心函数,它处理了队列满时的阻塞和超时逻辑。

5.1 函数签名
BaseType_t xQueueGenericSend(QueueHandle_t xQueue,const void * const pvItemToQueue,TickType_t xTicksToWait,const BaseType_t xCopyPosition )
参数说明:
xQueue:队列句柄 pvItemToQueue:待发送数据的指针 xTicksToWait:队列满时的等待超时(tick 数) xCopyPosition:发送位置(queueSEND_TO_BACK、queueSEND_TO_FRONT、queueOVERWRITE)
5.2 核心逻辑
for( ;; ) // 无限循环,直到成功或超时{taskENTER_CRITICAL();{// 检查是否有空间(或允许覆盖)if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||( xCopyPosition == queueOVERWRITE ) ){// 有空间:复制数据xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue,xCopyPosition );// 唤醒等待接收的任务if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList(&( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){// 被唤醒的任务优先级更高,需要上下文切换queueYIELD_IF_USING_PREEMPTION();}}taskEXIT_CRITICAL();return pdPASS;}else // 队列满{if( xTicksToWait == 0 ){// 不等待,立即返回taskEXIT_CRITICAL();return errQUEUE_FULL;}else if( xEntryTimeSet == pdFALSE ){// 记录进入时间vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}}}taskEXIT_CRITICAL();// 以下在非临界区执行vTaskSuspendAll(); // 挂起调度器prvLockQueue( pxQueue ); // 锁定队列// 检查是否超时if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){// 未超时,检查队列是否仍然满if( prvIsQueueFull( pxQueue ) != pdFALSE ){// 仍然满,阻塞等待vTaskPlaceOnEventList(&( pxQueue->xTasksWaitingToSend ), xTicksToWait );prvUnlockQueue( pxQueue );if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}}else{// 队列有空间了,重试prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{// 超时,返回失败prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();return errQUEUE_FULL;}}
5.3 关键设计点
双重检查模式:
第一次检查在临界区内(快速路径) 第二次检查在锁定队列后(慢速路径,处理竞态条件) 调度器挂起与队列锁定:
vTaskSuspendAll()防止任务切换 prvLockQueue()防止 ISR 修改事件列表两者配合确保操作的原子性 超时处理:
使用 xTimeOut结构体记录进入时间每次循环检查是否超时 超时后返回 errQUEUE_FULL
5.4 ISR 版本
xQueueGenericSendFromISR() 是中断安全版本:
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,const void * const pvItemToQueue,BaseType_t * const pxHigherPriorityTaskWoken,const BaseType_t xCopyPosition ){uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||( xCopyPosition == queueOVERWRITE ) ){// 复制数据( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );// 如果队列未锁定,直接唤醒任务if( cTxLock == queueUNLOCKED ){if( listLIST_IS_EMPTY(&( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList(&( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){// 需要上下文切换*pxHigherPriorityTaskWoken = pdTRUE;}}}else{// 队列已锁定,增加锁计数pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}xReturn = pdPASS;}else{xReturn = errQUEUE_FULL;}}portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;}
ISR 版本的特点:
不阻塞(无 xTicksToWait参数)使用 portSET_INTERRUPT_MASK_FROM_ISR()替代临界区通过 pxHigherPriorityTaskWoken返回是否需要上下文切换
6. 消息接收机制
xQueueReceive() 是接收操作的核心函数,其逻辑与发送类似但方向相反。

6.1 核心逻辑
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait ){for( ;; ){taskENTER_CRITICAL();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;if( uxMessagesWaiting > 0 ){// 有数据:复制数据prvCopyDataFromQueue( pxQueue, pvBuffer );pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;// 唤醒等待发送的任务if( listLIST_IS_EMPTY(&( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList(&( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){queueYIELD_IF_USING_PREEMPTION();}}taskEXIT_CRITICAL();return pdPASS;}else // 队列空{if( xTicksToWait == 0 ){taskEXIT_CRITICAL();return errQUEUE_EMPTY;}// ... 超时处理逻辑(与发送类似)}}taskEXIT_CRITICAL();// 阻塞等待...}}
6.2 Peek 操作
xQueuePeek() 读取但不移除数据:
BaseType_t xQueuePeek( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait ){// 保存原始读取位置pcOriginalReadPosition = pxQueue->u.xQueue.pcReadFrom;prvCopyDataFromQueue( pxQueue, pvBuffer );// 恢复读取位置(数据不移除)pxQueue->u.xQueue.pcReadFrom = pcOriginalReadPosition;// 注意:仍然唤醒其他等待任务// 因为数据仍在队列中,其他任务也可以读取}
7. 队列锁与 ISR 安全
7.1 队列锁定机制
队列锁定用于在批量操作期间提高效率:
// 锁定队列#define prvLockQueue( pxQueue )taskENTER_CRITICAL();{if( ( pxQueue )->cRxLock == queueUNLOCKED ){( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;}if( ( pxQueue )->cTxLock == queueUNLOCKED ){( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;}}taskEXIT_CRITICAL()
7.2 锁计数器的作用
当队列被锁定时,ISR 不能直接修改事件列表,而是增加锁计数器:
// ISR 中的发送操作if( cTxLock == queueUNLOCKED ){// 队列未锁定,直接唤醒任务xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) );}else{// 队列已锁定,只增加计数pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}
7.3 解锁时处理
prvUnlockQueue() 在解锁时处理积压的操作:
static void prvUnlockQueue( Queue_t * const pxQueue ){taskENTER_CRITICAL();{int8_t cTxLock = pxQueue->cTxLock;// 处理锁定期间的发送操作while( cTxLock > queueLOCKED_UNMODIFIED ){if( listLIST_IS_EMPTY(&( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList(&( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){// 记录需要上下文切换vTaskMissedYield();}}else{break;}--cTxLock;}pxQueue->cTxLock = queueUNLOCKED;}taskEXIT_CRITICAL();// 类似处理 cRxLock...}
设计优势:
减少了 ISR 中的操作时间 批量唤醒任务,提高效率 保证了事件列表的一致性
8. 阻塞与超时机制
8.1 阻塞流程
当队列满/空时,任务进入阻塞状态:
// 1. 记录进入时间vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;// 2. 挂起调度器vTaskSuspendAll();// 3. 锁定队列prvLockQueue( pxQueue );// 4. 检查超时if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){// 未超时if( prvIsQueueFull( pxQueue ) != pdFALSE ){// 仍然满,加入等待列表vTaskPlaceOnEventList(&( pxQueue->xTasksWaitingToSend ), xTicksToWait );}}// 5. 解锁队列prvUnlockQueue( pxQueue );// 6. 恢复调度器if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API(); // 需要上下文切换}
8.2 超时检测
xTaskCheckForTimeOut() 检查是否已超时:
BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait ){// 计算已过去的时间// 如果超时,返回 pdTRUE// 否则,更新剩余等待时间,返回 pdFALSE}
8.3 唤醒机制
当队列状态改变时,等待的任务被唤醒:
// 从等待列表移除任务BaseType_t xTaskRemoveFromEventList(const List_t * const pxEventList ){// 移除优先级最高的任务// 如果该任务优先级高于当前任务,返回 pdTRUE// 调用者应触发上下文切换}
9. 总结
9.1 核心设计要点
环形缓冲区:高效的数据存储,O(1) 入队出队 按值复制:避免指针问题,提高安全性 优先级等待列表:保证高优先级任务优先响应 队列锁机制:平衡 ISR 响应速度和操作原子性 结构复用:队列、信号量、互斥量共用同一结构
9.2 性能考量
内存分配:一次分配减少碎片 临界区最小化:只在必要时使用临界区 批量操作:队列锁减少 ISR 开销 优先级排序:确保实时性
9.3 使用建议
队列深度:根据生产者/消费者速率差设置 消息大小:尽量小,大数据传递指针 超时设置:避免无限阻塞导致死锁 ISR 操作:始终使用 FromISR版本
9.4 源码导航
xQueueGenericCreate | |
xQueueGenericReset | |
xQueueGenericSend | |
xQueueReceive | |
prvCopyDataToQueue | |
prvCopyDataFromQueue | |
prvLockQueue | |
prvUnlockQueue |
历史系列文章
告别硬件开发板,手把手教你零成本学习 FreeRTOS
FreeRTOS 链表:从数据结构到调度内核的基石
FreeRTOS 从创建第一个任务到任务启动 — 完整流程深度解析
夜雨聆风