Freertos中队列头尾指针及读写指针工作机制

张开发
2026/4/17 19:20:53 15 分钟阅读

分享文章

Freertos中队列头尾指针及读写指针工作机制
1.数据结构定义// 简化版队列结构体仅保留指针相关成员typedef struct QueueDefinition{int8_t *pcHead; // 队列存储区起始地址int8_t *pcWriteTo; // 写指针指向下一个可写入的位置union{struct{int8_t *pcTail; // 队列存储区结束地址最后一个字节的下一个位置int8_t *pcReadFrom; // 读指针指向最后一次读取的末尾经典实现} xQueue;// 其他成员用于互斥量等省略} u;UBaseType_t uxLength; // 队列长度消息个数UBaseType_t uxItemSize; // 每个消息的字节数UBaseType_t uxMessagesWaiting; // 当前消息数量} Queue_t;2. 队列创建与初始化假设我们创建一个能存放3个uint32_t类型数据的队列每个消息 4 字节。QueueHandle_t xQueue xQueueCreate(3, sizeof(uint32_t));QueueHandle_t xQueue xQueueCreate(3, sizeof(uint32_t)) { xQueueGenericCreate() { pxNewQueue ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) xQueueSizeInBytes ); pucQueueStorage ( uint8_t * ) pxNewQueue;//消息头部地址 pucQueueStorage sizeof( Queue_t ); //消息内容地址 prvInitialiseNewQueue() { // 1. 设置存储区边界 pxNewQueue-pcHead ( int8_t * ) pucQueueStorage; xQueueGenericReset() { pxQueue-u.xQueue.pcTail pxQueue-pcHead ( pxQueue-uxLength * pxQueue-uxItemSize ); // 2. 写指针指向头部 pxQueue-pcWriteTo pxQueue-pcHead; // 3. 读指针指向存储区的最后一个消息的起始位置关键 pxQueue-u.xQueue.pcReadFrom pxQueue-pcHead ( ( pxQueue-uxLength - 1U ) * pxQueue-uxItemSize ); } } } }初始化后内存示意图如下假设起始地址 0x1000地址: 0x1000 0x1004 0x1008 0x100C |-------------|-------------|-------------| 存储区: | 消息槽 1 | 消息槽 2 | 消息槽 3 | |-------------|-------------|-------------| ^ ^ pcHead ^ pcTail pcwriteTo pcReadFrom消息边界头pcHead 0x1000 消息边界尾pcTail 0x100C (0x1000 12) 写指针pcWriteTo 0x1000 读指针pcReadFrom 0x1008 (0x100C - 4) 消息计数器uxMessagesWaiting 0队列为空。3. 发送一个消息到队列尾部写入调用xQueueSend()写入一个值比如0x12345678。xQueueSend() { xQueueGenericSend() { prvCopyDataToQueue() { else if( xPosition queueSEND_TO_BACK ) { //将数据拷贝到 pcWriteTo 指向的位置 memcpy( pxQueue-pcWriteTo, pvItemToQueue, pxQueue-uxItemSize ); // 移动写指针 pxQueue-pcWriteTo pxQueue-uxItemSize; // 如果写指针到达尾部回绕到头部 if( pxQueue-pcWriteTo pxQueue-u.xQueue.pcTail ) { pxQueue-pcWriteTo pxQueue-pcHead; } } // 消息计数增加 pxQueue-uxMessagesWaiting; } } }地址: 0x1000 0x1004 0x1008 0x100C |-------------|-------------|-------------| 存储区: |0x12345678 | 消息槽 2 | 消息槽 3 | |-------------|-------------|-------------| ^ ^ pcHead ^ ^ pcTail pcwriteTo pcReadFrom 写入前: pcWriteTo 0x1000 写入后: pcWriteTo 0x1004 (指向槽2)先拷贝数据再移动写指针读指针不变: pcReadFrom 0x1008 (仍指向槽3) 消息计数器uxMessagesWaiting 14. 接收一个消息到队列读取调用xQueueReceive()读取数据。xQueueReceive() { prvCopyDataFromQueue() { // 1. 先移动读指针经典实现 pxQueue-u.xQueue.pcReadFrom pxQueue-uxItemSize; // 2. 如果移动后到达或超过尾部回绕到头部 if (pxQueue-u.xQueue.pcReadFrom pxQueue-pcTail) { pxQueue-u.xQueue.pcReadFrom pxQueue-pcHead; } // 3. 从新的 pcReadFrom 位置拷贝数据 memcpy(pvBuffer,pxQueue-u.xQueue.pcReadFrom, pxQueue-uxItemSize ); } // 4. 消息计数减1 pxQueue-uxMessagesWaiting uxMessagesWaiting - ( UBaseType_t ) 1; }地址: 0x1000 0x1004 0x1008 0x100C |-------------|-------------|-------------| 存储区: |0x12345678 | 消息槽 2 | 消息槽 3 | |-------------|-------------|-------------| ^ ^ pcHead ^ pcTail pcReadFrom pcwriteTo 读取前: pcReadFrom 0x1008 读取后: pcReadFrom 0x1000 (移动指针后到达尾部回绕到头部)先移动读指针后再拷贝数据读出0x12345678 写指针不变: pcWriteTo 0x1008 (仍指向槽2) 消息计数器uxMessagesWaiting 05.发送一个消息到队列头部在序列3基础上写入调用 xQueueSendToFront()写入一个值比如0x87654321。xQueueSendToFront() { xQueueGenericSend() { prvCopyDataToQueue() { else if( xPosition queueSEND_TO_BACK ) { ... } else { //将数据拷贝到 pcReadFrom指向的位置 memcpy( pxQueue-u.xQueue.pcReadFrom, pvItemToQueue,pxQueue-uxItemSize ) // 移动读指针 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; } } }地址: 0x1000 0x1004 0x1008 0x100C |-------------|-------------|-------------| 存储区: |0x12345678 | 消息槽 2 | 0x87654321 | |-------------|-------------|-------------| ^ ^ pcHead ^ pcTail pcwriteTo pcReadFrom 写入前: pcReadFrom 0x1008先拷贝数据再移动读指针写入后: pcReadFrom 0x1004 写指针不变: pcWriteTo 0x1004 (仍指向槽1) 消息计数器uxMessagesWaiting 2 此时若读取一条消息则读指针4后拷贝数据读出头部消息0x87654321以上例程基于队列未满时写入、队列有消息时读出。其他情况可对照源码查看本文旨在说明一般情况下队列读写时读指针与写指针的工作机制。核心设计思想pcWriteTo永远指向下一个可写入的空槽。pcReadFrom永远指向下一个将要读取的消息的前一个位置经典实现中先移动再读取使得 FIFO 顺序和环形缓冲区完美结合。通过回绕机制实现存储区的循环利用无需移动数据。这种设计使得队列操作非常高效只需要简单的指针加减和比较无需复杂的内存搬移。

更多文章