目录
从队列读取数据API函数(出队):xQueueReceive()
简介:
队列是任务到任务,任务到中断,中断到任务数据交流的一种机制(消息传递)。
其作用与全局变量类似,但又存在差距。
假设一个全局变量a = 0;现在两个任务都在写这个变量,且均在执行a++程序
任务一() 任务二()
{ {
a++; a++;
} }
如果任务一与任务二各执行一次,理论上a应该等于2。但是呢a++在c语言中内部又分为多个操作:
1.寄存器读数据(r0 <= a);
2.寄存器修改数据(r0 = r0 + 1);
3.变量读取寄存器数据(r0 => a);
在裸机操作中,并没有创建的多个任务进行干扰此过程,而在OS中,便会存在这种可能,比如:
任务一执行到a++内部操作的第二步时,被更高优先级的任务二打断,那么会出现什么情况呢?
此时r0寄存器已经读取到了变量a的值,r0+1此时r0的值为1,但是变量a还未从r0中读取数据,而这时候CPU便去执行优先级更高的任务二,任务二同样的也是这三个过程,此时的变量a的值还是0,执行完毕后a = 1;任务二进入阻塞态,CPU又重新回到任务一的第三步,这时候r0 = 1,a赋值后仍然等于1。这样的话,执行了两个任务,但是它的实际效果a=1,造成了数据受损。所以,全局变量在OS中存在一个弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据容易受损。
那么在队列中,此过程又是怎么实现的呢?
此时任务A与任务B也是同时写队列,是否也会存在干扰呢?然而写队列内部函数的操作中会进入临界区,也就是关中断。
任务调度是由PendSV引起的,而PendSV的中断优先级在正常情况下设置的是最低的,此时进入临界区关掉了所有的中断,此时PendSV不可能触发。所以任务A在写队列的时候,任务B是不可能打断其运行。此时数据的安全性得到了保障,相同的,读队列也是同样的道理。
读写队列做好了保护,防止多任务同时访问冲突;我们只需要直接调用API函数即可,简单易用。
FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此很有必要深入了解FreeRTOS的队列。
队列的特征 :
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。(跟c语言中的数组有些类似)
在创建队列时,就要指定队列长度以及对列项目的大小。
FreeRTOS队列特点:
如果设置的阻塞时间为0:且恰好所有的队列项目中均有数据,此时便会直接返回退出不会进行等待;
如果设置的阻塞时间为0~port_MAX_DELAY(0xFFFFFFFF):等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
如果设置的阻塞时间为port_MAX_DELAY:死等,一直等到队列空出一个位置可以进行入队操作为止。
出队阻塞与入队阻塞类似。
入队阻塞:
此时队列已满,写不进数据:
1.将该任务的状态列表项挂载在pxDelayedTaskList(阻塞列表)中;
2.将该任务的事件列表项挂载在xTaskWaitingToSend(等待发送列表)中;
出队阻塞:
队列为空,此时读取不了数据:
1.将该任务的状态列表项挂载在pxDelayedTaskList(阻塞列表)中;
2.将该任务的事件列表项挂载在xTaskWaitingToReceive(等待读取列表)中;
问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一队列的空间。那当队列中有空间时,哪个任务会进入就绪态?
答:
1.优先级最高的任务
2.如果这些任务的优先级相同,那等待时间最久的任务会进入就绪态
队列操作的基本过程:
队列结构体介绍:
typedef struct QueueDefinetion
{
int8_t * pcHead; /*存储区域的起始位置(队列内存可分为两部分,第一部分为
存放整个结构体,第二部分为队列里面的队列项,写入数据
及写入到队列项中。存储区域的起始地址表示队列项的起
始地址)*/
int8_t * pcWriteTo; /*下一个写入的位置(写在哪个队列项中由此指定)*/
/* 信号量是由队列实现的,
* 此结构体能用于队列和信号量,
* 当用于队列时,使用联合体中的 xQueue,
* 当用于信号量时,使用联合体中的 xSemaphore
*/
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
}u; /*联合体*/
List_t xTasksWaitingToSend; /*等待发送列表*/
List_t xTasksWaitingToReceive; /*等待接收列表*/
volatile UBaseType_t uxMessagesWaiting; /*非空闲队列项目的数量*/
UBaseType_t uxLength; /*队列长度(队列中队列项目的总量)*/
UBaseType_t uxItemSize; /*队列项目的大小(字节)*/
/* 锁用于在任务因队列操作被阻塞前,防止中断或其他任务操作队列。
* 上锁期间,队列可以写入和读取消息,但不会操作队列阻塞任务列表,
* 当有消息写入时,cTxLock 加 1,当有消息被读取时,cRxLock 加 1,
* 在解锁时,会统一处理队列的阻塞任务列表
*/
volatile int8_t cRxLock; /*读取上锁计数器(上锁后,此时仍可以正常读写队列,只
是操作不了等待发送列表)*/
volatile int8_t cTxLock; /*写入上锁计数器(效果同上,对应接收)*/
/*其他的一些编译条件*/
}xQUEUE;
当用于队列使用时:
typedef struct QueuePointers
{
int8_t * pcTail; /*存储区的结束地址*/
int8_t * pcReadFrom; /*最后一个读取队列的地址*/
}QueuePointers_t;
当用于互斥信号量和递归互斥信号量时:
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /*互斥信号量持有者(信号量属于谁)*/
UBaseType_t uxRecursiveCallCount; /*递归互斥信号量的获取计数器*/
}SemaphoreData_t;
队列结构体整体示意图:
pcHead指向起始地址;pcWriteTo(下一次写入的位置)也是从起始地址开始;xQueue.pcTail是结束地址;xQueue.pcReadFrom是最后一个读取的地址; xTasksWaitingToSend为等待发送列表; xTasksWaitingToReceive为等待接收列表;uxMessagesWaiting非空闲队列数目,此时队列项均为空闲,值为0;uxLength队列长度,即队列项数目;uxItemSize一个队列项的大小,此时为32字节;queueUNLOCKED表示两个列锁均未上锁。
队列相关API函数介绍:
使用队列的主要流程:创建队列—>写队列—>读队列。
创建队列相关API函数介绍:
函数 | 描述 |
xQueueCreate() | 动态方式创建队列 |
xQueueCreateStatic() | 静态方式创建队列 |
动态和静态创建队列之间的区别:队列所需的内存空间由FreeRTOS从FreeRTOS管理的堆中分配,而静态创建需要用户自行分配内存。
此函数用于·使用动态创建队列,队列所需的内存空间由FreeRTOS从FreeRTOS管理的堆中分配,动态创建队列函数xQueueCreate()实际上是一个宏,真正起作用的是xQueueGenericCreate()函数
形参 | 描述 |
xQueueLength | 队列长度 |
uxItemSize | 队列项目的大小 |
返回值 | 描述 |
NULL | 队列创建失败 |
其他值 | 队列创建成功,返回队列句柄 |
前面说FreeRTOS基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的queue.h文件中有定义:
#define queueQUEUE_TYPE_BASE ( (uint8_t) 0U ) /*队列*/
#define queueQUEUE_TYPE_SET ( (uint8_t) 0U ) /*队列集*/
#define queueQUEUE_TYPE_MUTEX ( (uint8_t) 1U ) /*互斥信号量*/
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( (uint8_t) 2U ) /* 计数型信号量*/
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( (uint8_t) 3U ) /*二值信号量*/
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( (uint8_t) 4U ) /*递归互斥信号量*/
对列写入消息API函数:
函数 | 描述 |
xQueueSend() | 往队列的尾部写入消息 |
xQueueSendToBack() | 同xQueueSend() |
xQueueSendToFront() | 往队列的头部写入消息 |
xQueueOverWrite() | 覆写队列消息(只用于队列长度为1的情况) |
xQueueSendFromISR() | 在中断中往队列的尾部写入消息 |
xQueueSendToBackFromISR() | 同xQueueSendFromISR() |
xQueueSendToFrontFromISR() | 在中断中往队列的头部写入消息 |
xQueueOverWriteFromISR() | 在中断中覆写队列消息(只用于队列长度为1的情况) |
在这里可以看成两部分,前四个为一部分,在任务中使用,后四个在中断中使用。下面只展示任务集使用的函数,中断函数可查阅相关手册
四个函数首先都是宏,可以看出这几个写入函数调用的是同一个函数xQueueGenericSend(),只是指定了不同的写入位置,即最后一个参数。最后一个函数没有阻塞时间可以设置,默认为0。覆写指只有一个队列项目,且里面已经存放数据,也可覆盖写入数据,不会进入阻塞。
队列一共有3种写入位置:
#define queueSEND_TO_BACK ((BaseType_t) 0 ) /*写入队列尾部*/
#define queueSEND_TO_FRONT ((BaseType_t) 1 ) /*写入队列头部*/
#define queueOVERWRITE ((BaseType_t) 2 ) /*覆写队列*/
注意:覆写方式写入队列,只有在队列的队列长度为1时,才能够使用。
往队列写入消息函数入口参数解析:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvltemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPostion )
形参 | 描述 |
xQueue | 待写入消息的队列 |
pvItemToQueue | 待写入消息 |
xTicksToWait | 阻塞超时时间 |
xCopyPostion | 写入的位置 |
返回值 | 描述 |
pdTRUE | 队列写入成功 |
errQUEUE_FULL | 队列写入失败 |
从队列读取消息API函数:
函数 | 描述 |
xQueueReceive() | 从队列头部读取消息,并删除消息 |
xQueuePeek() | 从队列头部读取消息 |
xQueueReceiveFromISR() | 在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromISR() | 在中断中从队列头部读取消息 |
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。
形参 | 描述 |
xQueue | 待读取的队列 |
pvBuffer | 信息读取缓冲区 |
xTicksToWait | 阻塞超时时间 |
返回值 | 描述 |
pdTRUE | 读取成功 |
pdFALSE | 读取失败 |
BaseType_t xQueuePeek ( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
此函数用于在任务中,从队列中读取消息,但与函数xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息。
形参 | 描述 |
xQueue | 待读取的队列 |
pvBuffer | 信息读取缓冲区 |
xTicksToWait | 阻塞超时时间 |
返回值 | 描述 |
pdTRUE | 读取成功 |
pdFALSE | 读取失败 |
队列相关API函数解析:
队列的创建API函数:xQueueCreate()
#define xQueueCreate( uxQueueLength, uxItemSize )
xQueueGenericCreate( ( uxQueueLength ),
( uxItemSize ),
( queueQUEUE_TYPE_BASE ) )
在queue.h文件中,xQueueCreate()函数是以宏定义的形式出现,有两个入口参数,分别是队列长度以及队列项目大小 。实际操作的是xQueueGenericCreate()函数,参数多了一个创建队列类型,queueQUEUE_TYPE_BASE 表示创建的是队列。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
{
Queue_t * pxNewQueue = NULL;
size_t xQueueSizeInBytes;
uint8_t * pucQueueStorage;
if( ( uxQueueLength > ( UBaseType_t ) 0 ) &&
/*首先对队列长度进行判断,判断是否大于0 */
( ( SIZE_MAX / uxQueueLength ) >= uxItemSize ) &&
/* 后两个均是判断队列是否溢出 */
( ( SIZE_MAX - sizeof( Queue_t ) ) >= ( uxQueueLength * uxItemSize ) ) )
/* if作用:判断数据合法性*/
{
/*计算队列项长度的总大小 */
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*一个队列项大
小 * 总的队列项数目 */
/* 开始申请内存,队列内存内部总共分为两部分,一个是队列结构体存储区,另一个是队列项
(消息存储区),申请完内存后把首地址传给pxNewQueue */
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); /*Queue_t 表示结构体存储区, xQueueSizeInBytes 表示队列项存储区*/
if( pxNewQueue != NULL )/*判断是否申请成功*/
{
/*首先让pucQueueStorage等于首地址,然后向上偏移到队列结构体存储区的末尾也就是队
列项的首地址*/
pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t ); /*此时 pucQueueStorage 等于队列项一的
起始地址*/
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Queues can be created either statically or dynamically, so
* note this task was created dynamically in case it is later
* deleted. */
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
/*初始化队列,五个入口参数分别是队列项个数、队列项大小、队列项(消息存储区)首地
址、创建的队列类型、队列首地址*/
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}
else
{
traceQUEUE_CREATE_FAILED( ucQueueType );
mtCOVERAGE_TEST_MARKER();
}
}
else
{
configASSERT( pxNewQueue );
mtCOVERAGE_TEST_MARKER();
}
return pxNewQueue;
}
初始化队列 prvInitialiseNewQueue()函数的解析:
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t * pucQueueStorage,
const uint8_t ucQueueType,
Queue_t * pxNewQueue )
{
/* Remove compiler warnings about unused parameters should
* configUSE_TRACE_FACILITY not be set to 1. */
( void ) ucQueueType;
/*首先判断队列项大小是不是等于0,若等于0,说明此时并非队列而是信号量*/
if( uxItemSize == ( UBaseType_t ) 0 )
{
/*把头地址指向结构体存储区首地址 */
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
/* 若不等于0,把头指针指向消息存储区首地址 */
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
/* 接着赋值队列项长度与大小 */
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
/*调用函数,复位队列,此时队列可能是新旧队列两种情况,pdTRUE代表新创建的队列*/
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewQueue->ucQueueType = ucQueueType;
}
#endif /* configUSE_TRACE_FACILITY */
#if ( configUSE_QUEUE_SETS == 1 )
{
pxNewQueue->pxQueueSetContainer = NULL;
}
#endif /* configUSE_QUEUE_SETS */
traceQUEUE_CREATE( pxNewQueue );
}
复位队列函数xQueueGenericReset()解析:
BaseType_t xQueueGenericReset( QueueHandle_t xQueue,
BaseType_t xNewQueue )
{
BaseType_t xReturn = pdPASS;
/*将队列首地址赋值*/
Queue_t * const pxQueue = xQueue;
configASSERT( pxQueue );
/*判断内存是否申请成功,并且长度大于等于1*/
if( ( pxQueue != NULL ) &&
( pxQueue->uxLength >= 1U ) &&
/* 判断是否有溢出 */
( ( SIZE_MAX / pxQueue->uxLength ) >= pxQueue->uxItemSize ) )
{
taskENTER_CRITICAL();/*进入临界区*/
{
/*对成员变量进行赋值*/
pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); /*pcHead此时为消息存储区首地址,加上队列项总长度,此时pcTail 指向队列末尾 */
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;/*非空闲队列项数目*/
pxQueue->pcWriteTo = pxQueue->pcHead; /*下一个要写入的位置,此时指向头*/
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize ); /*头地址+ (队列大小-1)* 队列项大小 ,表示指向最后一个元素*/
/*队列锁均未上锁*/
pxQueue->cRxLock = queueUNLOCKED;
pxQueue->cTxLock = queueUNLOCKED;
/*判断是否为旧队列*/
if( xNewQueue == pdFALSE )
{
/* 如果为旧列表,判断当前等待发送列表是不是有任务在里面,若有任务,则直接移除 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 若为新列表,直接初始化等待发送与等待接收两个列表*/
vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
}
}
taskEXIT_CRITICAL();/*退出临界区*/
}
else
{
xReturn = pdFAIL;
}
configASSERT( xReturn != pdFAIL );
/* 返回一个返回值 */
return xReturn;
}
往队列写入数据API函数(入队):xQueueSend()
在任务中往队列写入消息的函数有函数 xQueueSend() 、 xQueueSendToBack() 、 xQueueSendToFront()、xQueueOverwrite(),这 4 个函数实际上都是宏定义,在 queue.h 文件中有 定义,具体的代码如下所示:
#define xQueueSend( xQueue, \
pvItemToQueue, \
xTicksToWait) \
xQueueGenericSend( ( xQueue ), \
( pvItemToQueue ), \
( xTicksToWait ), \
queueSEND_TO_BACK)
#define xQueueSendToBack( xQueue, \
pvItemToQueue, \
xTicksToWait) \
xQueueGenericSend( ( xQueue ), \
( pvItemToQueue ), \
( xTicksToWait ), \
queueSEND_TO_BACK)
#define xQueueSendToFront( xQueue, \
pvItemToQueue, \
xTicksToWait) \
xQueueGenericSend( ( xQueue ), \
( pvItemToQueue ), \
( xTicksToWait ), \
queueSEND_TO_FRONT)
#define xQueueOverwrite( xQueue, \
pvItemToQueue) \
xQueueGenericSend( ( xQueue ), \
( pvItemToQueue ), \
0,\
queueOVERWRITE)
从上面的代码中可以看到,函数 xQueueSend()、函数 xQueueSendToBack()、函数 xQueueSendToFront()和函数 xQueueOverwrite()实际上都是调用了函数 xQueueGenericSend(),只 是指定了不同的写入位置,队列一共有 3 种写入位置,在 queue.h 文件中有定义,具体的代码如下
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) /* 写入队列尾部 */
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) /* 写入队列头部 */
#define queueOVERWRITE ( ( BaseType_t ) 2 ) /* 覆写队列 */
要注意的是,覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用,这在下文 讲解函数 xQueueGenericSend()时,会提到。 函 数 xQueueGenericSend() 用 于 在 任 务 中 往 队 列 的 指 定 位 置 写 入 消 息 。函数 xQueueGenericSend()在 queue.c 文件中有定义,具体的代码如下所示:
/*四个参数分别是 要写入的队列、要写入的消息、阻塞的时间、写入的位置*/
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
{
BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
configASSERT( pxQueue );
configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
/* 这里限制了只有在队列长度为 1 时,才能使用覆写,如果是覆写但队列长度不等于1则会报错*/
configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/*lint -save -e904 This function relaxes the coding standard somewhat to
* allow return statements within the function itself. This is done in the
* interest of execution time efficiency. */
for( ; ; )
{
taskENTER_CRITICAL();/*进入临界区*/
{
/*判断 非空闲队列项数目 是否小于 队列长度 ,或者入队方式是否为覆写*/
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
/* 用于调试,不用理会 */
traceQUEUE_SEND( pxQueue );
/*判断是否为队列集*/
#if ( configUSE_QUEUE_SETS == 1 )
{
/* 获取队列中非空闲项目的数量 */
const UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;
/* 将待写入消息按指定写入方式复制到队列中 */
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* 判断队列是否在队列集中 */
if( pxQueue->pxQueueSetContainer != NULL )
{
/* 写入位置为覆写,且队列非空闲项目数量不为 0 */
if( ( xCopyPosition == queueOVERWRITE ) && ( uxPreviousMessagesWaiting != ( UBaseType_t ) 0 ) )
{
/* Do not notify the queue set as an existing item
* was overwritten in the queue so the number of items
* in the queue has not changed. */
mtCOVERAGE_TEST_MARKER();
}
/* 通知队列集 */
else if( prvNotifyQueueSetContainer( pxQueue ) != pdFALSE )
{
/* 根据需要进行任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 队列不在队列集中 */
else
{
/* 队列的读取阻塞任务列表非空 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
/* 将队列读取阻塞任务从所在列表移除
* 因为此时队列中已有可用消息*/
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 根据需要进行任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xYieldRequired != pdFALSE )
{
/*在互斥信号量释放完且任务优先级恢复后需要进行任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
#else /*若为队列操作*/
{
/*将消息写入到队列存储区域的指定位置,三个参数:目标队列、传入的数据、
拷贝的位置 (尾部,头部或覆写)*/
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* 判断等待接收列表里面是否有任务,队列有阻塞的读取任务 * */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{ /*将读取阻塞任务从队列读取任务阻塞列表中移除,
因为此时,队列中已经有非空闲的项目了*/
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/*有任务解除阻塞后需要根据任务的优先级进行任务切换*/
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xYieldRequired != pdFALSE )
{
/*在互斥信号量释放完且任务优先级恢复后,
需要进行任务切换*/
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_QUEUE_SETS */
taskEXIT_CRITICAL();/*退出临界区*/
return pdPASS;/*写入队列成功*/
}
/*若此时队列已满并且不是覆写,此时将会进入阻塞*/
else
{
/*判断阻塞时间是否为0*/
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 退出临界区*/
taskEXIT_CRITICAL();
/* 用于调试,不用理会 */
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;/*返回队列已满*/
}
/*若阻塞时间不等于0*/
else if( xEntryTimeSet == pdFALSE )
{
/*队列满,任务需要阻塞,记录下此时系统节拍
计数器的值和溢出次数用于下面对阻塞时间进行补偿 */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
/* 退出临界区
* 退出临界区后系统时钟节拍会发生更新,
* 因此任务如果需要阻塞的话,
* 需要对阻塞时间进行补偿*/
taskEXIT_CRITICAL();
/* 挂起任务调度器 */
vTaskSuspendAll();
/* 队列上锁 */
prvLockQueue( pxQueue );
/*判断阻塞时间补偿后,是否还需要阻塞 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
/* 阻塞时间补偿后,还需要进行阻塞 */
if( prvIsQueueFull( pxQueue ) != pdFALSE )
{
/* 用于调试,不用理会 */
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
/* 将任务添加到队列写入阻塞任务列表中进行阻塞 */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
/* 解锁队列 */
prvUnlockQueue( pxQueue );
/* 恢复任务调度器 */
if( xTaskResumeAll() == pdFALSE )
{
/* 根据需要进行任务切换 */
portYIELD_WITHIN_API();
}
}
else
{
/* 队列解锁 */
prvUnlockQueue( pxQueue );
/* 恢复任务调度器 */
( void ) xTaskResumeAll();
}
}
/* 阻塞时间补偿后,已不需要阻塞 */
else
{
/* 解锁队列 */
prvUnlockQueue( pxQueue );
/* 恢复任务调度器 */
( void ) xTaskResumeAll();
/* 用于调试,不用理会 */
traceQUEUE_SEND_FAILED( pxQueue );
/* 返回队列满错误 */
return errQUEUE_FULL;
}
} /*lint -restore */
}
写入消息函数prvCopyDataToQueue()解析:
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
/* 获取队列项非空闲的数目 */
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/*判断队列项大小是否等于0,等于0为信号量*/
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
pxQueue->u.xSemaphore.xMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
/*写入位置如果是尾部插入(往上走)*/
else if( xPosition == queueSEND_TO_BACK )
{
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*pcWriteTo表示下次写入位置,初始状态指向头,将数据pvItemToQueue拷贝到
第一个队列项里面,大小为一个队列项大小*/
/*更新下次写入位置*/
pxQueue->pcWriteTo += pxQueue->uxItemSize;
/*如果下次写入等于尾部地址 */
if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail )
{
/*又回到队列项头部地址*/
pxQueue->pcWriteTo = pxQueue->pcHead;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/*头部写入或覆写(往下走)*/
else
{
( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*pcReadFrom 指向最后一个队列项元素,赋值*/
/*偏移pcReadFrom */
pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;
if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) /*如果下个地址小于头部地址*/
{
/*回到末尾元素地址*/
pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*如果写入方式为覆写*/
if( xPosition == queueOVERWRITE )
{
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/*若非空闲大于0,先让他减去1 */
--uxMessagesWaiting;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/*非空闲数目加1*/
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
return xReturn;
}
从队列读取数据API函数(出队):xQueueReceive()
此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。 消息的读取是通过拷贝的形式传递的,具体拷贝数据的大小,为队列项目的大小。 该函数的函 数原型如下所示:
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
/* Check the pointer is not NULL. */
configASSERT( ( pxQueue ) );
/* The buffer into which data is received can only be NULL if the data size
* is zero (so no data is copied into the buffer). */
configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );
/* Cannot block if the scheduler is suspended. */
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/*lint -save -e904 This function relaxes the coding standard somewhat to
* allow return statements within the function itself. This is done in the
* interest of execution time efficiency. */
for( ; ; )
{
taskENTER_CRITICAL();/*进入临界区*/
{
/*获取当前非空闲队列项数目*/
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/*判断当前队列是否有数据*/
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/*拷贝数据到 pvBuffer 里面 */
prvCopyDataFromQueue( pxQueue, pvBuffer );
traceQUEUE_RECEIVE( pxQueue );
/*非空闲数目减1*/
pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;
/* 如果等待发送列表有任务,则解除掉*/
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*退出临界区*/
taskEXIT_CRITICAL();
return pdPASS;
}
/*若当前队列里面并没有数据*/
else
{
/*判断阻塞时间是否为0*/
if( xTicksToWait == ( TickType_t ) 0 )
{
/*退出临界区*/
taskEXIT_CRITICAL();
/**/
traceQUEUE_RECEIVE_FAILED( pxQueue );
/*返回读取失败*/
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* 记录系统节拍计数值以及溢出次数*/
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* Interrupts and other tasks can send to and receive from the queue
* now the critical section has been exited. */
vTaskSuspendAll();
prvLockQueue( pxQueue );
/*检查阻塞时间是否已超时,pdFALSE 未超时 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
/*判断队列是否为空 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
/*任务列表项挂载到等待接收列表里面*/
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
/*解锁队列*/
prvUnlockQueue( pxQueue );
/*如果需要任务切换*/
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* The queue contains data again. Loop back to try and read the
* data. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
/*阻塞时间已过*/
else
{
/*解锁,恢复任务调度器 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
} /*lint -restore */
}
拷贝数据函数prvCopyDataFromQueue()解析:
static void prvCopyDataFromQueue( Queue_t * const pxQueue,
void * const pvBuffer )
{
/*如果队列项大小不等于零*/
if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
{
pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize; /* pcReadFrom 初始化指向最后
一个元素,加一个列表项大小,指向队列末尾*/
if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail ) /*如果指向末尾地址*/
{
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;/*pcReadFrom 指向队列项头*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*队列项1的数据拷贝到pvBuffer里面 */
( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
}
}