队列实质:队列是一个环形缓冲区,遵循先入先出(FIFO),通常将数据写入尾部,也可强制写到头部。当强制写到头部时,并不会覆盖原来的头部数据。
队列等待唤醒原则:唤醒最高优先级的任务。当优先级一致时,唤醒已经等待时间最长的任务
队列中包含的内容:数据buf,等待数据的任务,等待写数据的任务
队列结构体的声明如下:
typedef xQUEUE Queue_t;
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;
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated;
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
pcHead:指向数据空间的首地址,不会改变
pcWriteTo:向哪里进行写入,指向写入的位置
xQueue:读队列相关结构体,里面存放了上一次读取的位置:pcReadFrom
xTasksWaitingToSend:等待写入数据任务的队列
xTasksWaitingToReceive:等待读取数据任务的队列
补充:
xQueue结构体声明如下:
/* 队列结构体中的形式 */
QueuePointers_t xQueue; /* 读队列相关结构体 */
/* QueuePointers_t结构体声明 */
typedef struct QueuePointers
{
int8_t * pcTail;
int8_t * pcReadFrom; /* 读指针 */
} QueuePointers_t;
创建队列有两个接口,一个是宏xQueueCreate,另一个是这个宏的宏定义xQueueGenericCreate
这两个函数实质上都是一样的,具体的函数声明如下:
/* 这是一个宏 */
xQueueCreate( uxQueueLength, uxItemSize );
/* 宏定义 */
#define xQueueCreate( uxQueueLength, uxItemSize ) \
xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) );
/* 这是xQueueCreate的宏定义 */
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType );
返回值:创建成功返回队列的句柄,失败返回0
uxItemSize:每一个项Item的大小,假设为int,那么 传入值为sizeof(int)
uxQueueLength:队列的长度
补充:
创建队列函数会分配一个Queue_t类型的结构体,并且开辟一个空间,让结构体中的pchead、pcWriteTo指向空间的头部,让pcReadFrom指向空间的尾部。
创建后的模型如下:
写队列有三个接口,一个是宏xQueueSend,另一个是这个宏的宏定义xQueueGenericSend,还有一个函数xQueueSendToBack,这个函数与xQueueSend作用一致。
具体的函数声明如下:
/* 这两个是宏,作用一致,都是向尾部写入数据 */
xQueueSend( xQueue, pvItemToQueue, xTicksToWait );
xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait );
/* 宏定义 */
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
/* 宏定义函数 */
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
xQueue:队列的句柄,即:创建队列函数的返回值
pvItemToQueue:原始数据的指针
xTicksToWait :当队列为满时等待写入的时间。写0表示不等待,portMAX_DELAY表示死等
写队列的过程为:从pvItemToQueue指向的空间中拷贝到pcWriteTo指向的空间,拷贝的数据大小为uxItemSize。之后pcWriteTo+uxItemSize进行写指针偏移。
具体模型如下:
当写队列时,队列已经满了,并且xTicksToWait不为0,那么该写数据的任务将会放到xTasksWaitingToSend这个等待写入数据任务队列中,这时该任务进入阻塞态,不再争夺CPU资源。当队列数据被读出,不再为满时,再从xTasksWaitingToSend这个队列中去唤醒刚刚的任务。
一般写队列的方法都是尾插法,这里也可以进行头插法进行写队列。
/* 这是一个宏,头插法写入 */
xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait )
/* 宏定义 */
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
/* 最终调用的底层函数与尾插法一致,只是传参不一致 */
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
xQueue:队列的句柄,即:创建队列函数的返回值
pvItemToQueue:原始数据的指针
xTicksToWait :当队列为满时等待写入的时间。写0表示不等待
头插法写队列,会将队列写到空间的最尾端(N-1处),并且将pcReadFrom向前移动
具体模型如下:
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
xQueue:队列的句柄,即:创建队列函数的返回值
pvBuffer:读取数据存放空间的指针
xTicksToWait :当队列为空时等待读取的时间。写0表示不等待
pcReadFrom为上一次读的位置。读队列时,先将pcReadFrom+uxItemSize,之后再拷贝pcReadFrom指向位置的内容到pvBuffer中,拷贝的数据大小为uxItemSize
具体模型如下:
当读队列时,队列为空,并且xTicksToWait不为0,那么该读数据的任务将会放到xTasksWaitingToReceive这个等待读取数据任务队列中,这时该任务进入阻塞态,不再争夺CPU资源。。当队列数据被写入,不再为空时,再从xTasksWaitingToReceive这个队列中去唤醒刚刚的任务。