一.队列简介
在实际的应用中,常常会遇到一个任务或者中断服务需要和另外一个任务进行 “沟通交流”,这个 “沟通交流” 的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到 “资源管理” 的问题。对此 FreeRTOS 提供了一个叫做 “队列” 的机制来完成任务与任务、任务与中断之间的消息传递。
队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列是用来传递消息的,所以也称为消息队列。
1.数据存储
通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫做入队)永远都是发送到队列的尾部,而从队列提取数据的时候(也叫做出队)是从队列的头部提取的。但是也可以使用 LIFO(后进先出)的存储缓冲,FreeRTOS 中的队列同样也提供了 LIFO 的存储缓冲机制。
数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。
2.多任务访问
队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。
3.出队阻塞
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是针对从队列中读取消息的任务而言的。
比如任务A用于处理串口接收到的数据,串口接收到数据以后就会放到队列Q中,任务A从队列Q中读取数据。但是如果此时队列Q是空的,说明还没有数据,任务A这时候来读取的话肯定是获取不到任何东西。任务A现在有三种选择,一:二话不说扭头就走,二:要不我在等等吧,等一会看看,说不定一会就有数据了,三:死等。选哪一个就是由这个阻塞时间决定的,这个阻塞时间 单位是时钟节拍数。阻塞时间为0的话就是不阻塞,没有数据的话就马上返回任务继续执行接下来的代码,对应第一种选择。如果阻塞时间为0~ portMAX_DELAY,当任务没有从队列中获取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还没有接收到数据的话就退出阻塞态,返回任务接着运行下面的代码,如果在阻塞时间内接收到了数据就立即返回,执行任务中下面的代码,这种情况对应第二种选择。当阻塞时间设置为 portMAX_DELAY 的话,任务就会一直进入阻塞态等待,直到接收到数据为止。
4.入队阻塞
入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。比如任务B向消息队列Q发送消息,但是此时队列Q是满的,那肯定是发送失败的。此时任务B就会遇到和上面任务A一样的问题,这两种情况的处理过程是类似的,只不过一个是向队列Q发送消息,一个是从队列Q读取消息而已。
5.队列操作过程
创建队列:
如上图所示,任务 A 要向任务 B 发送消息,这个消息的内容是 x 变量的值。此时首先需要创建一个队列,并且指定队列的长度和每条消息的长度。这里我们创建一个长度为 4 的队列,因为要传递的是 x 的值,而 x 是个 int 类型的变量,所以每条消息的长度就是 int 类型的长度,在 STM32 中就是 4 字节,即每条消息是 4 个字节的。
向队列发送第一个消息 :
如上图所示,任务 A 的变量 x 的值为 10,将这个值发送到消息队列中。此时队列剩余长度就是 3 了。在之前的学习过程中,我们已经了解到了向队列发送消息采用的是拷贝的方式,所以一旦消息发送完成变量 x 就可以再次被使用,赋其他的值。
向队列发送第二个消息 :
如上图所示,任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。此时队列剩余长度为 2。
从队列中读取消息:
如上图所示,任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10 了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变为 3。如果不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。
二.队列结构体
有一个结构体用于描述队列,叫做Queue_t,这个结构体在文件queue.c中定义如下:
typedef struct QueueDefinition
{
int8_t *pcHead; //指向队列存储区开始地址。
int8_t *pcTail; //指向队列存储区最后一个字节。
int8_t *pcWriteTo; //指向存储区中下一个空闲区域。
union
{
int8_t *pcReadFrom; //当用作队列的时候指向最后一个出队的队列项首地址
UBaseType_t uxRecursiveCallCount;//当用作递归互斥量的时候用来记录递归互斥量被
//调用的次数。
} u;
List_t xTasksWaitingToSend; //等待发送任务列表,那些因为队列满导致入队失败而进
//入阻塞态的任务就会挂到此列表上。
List_t xTasksWaitingToReceive; //等待接收任务列表,那些因为队列空导致出队失败而进
//入阻塞态的任务就会挂到此列表上。
volatile UBaseType_t uxMessagesWaiting; //队列中当前队列项数量,也就是消息数
UBaseType_t uxLength; //创建队列时指定的队列长度,也就是队列中最大允许的
//队列项(消息)数量
UBaseType_t uxItemSize; //创建队列时指定的每个队列项(消息)最大长度,单位字节
volatile int8_t cRxLock; //当队列上锁以后用来统计从队列中接收到的队列项数
//量,也就是出队的队列项数量,当队列没有上锁的话此字
//段为 queueUNLOCKED
volatile int8_t cTxLock; //当队列上锁以后用来统计发送到队列中的队列项数量,
//也就是入队的队列项数量,当队列没有上锁的话此字
//段为 queueUNLOCKED
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) &&\
( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; //如果使用静态存储的话此字段设置为 pdTURE。
#endif
#if ( configUSE_QUEUE_SETS == 1 ) //队列集相关宏
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关宏
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
typedef xQUEUE Queue_t;
三.队列创建
在使用队列之前必须先创建队列,有两种创建队列的方法,一种是静态的,使用函数 xQueueCreateStatic();另一个是动态的,使用函数 xQueueCreate()。这两个函数本质上都是宏,真正完成队列创建的函数是 xQueueGenericCreate() 和 xQueueGenericCreateStatic(),这四个函数在文件 queue.c 中有定义:
1.函数 xQueueCreate()
此函数本质上是一个宏,用来动态创建队列,此宏最终调用函数xQueueGenericCreate(), 函数原型如下:
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
UBaseType_t uxItemSize)
uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
uxItemSize:队列中每个项目(消息)的长度,单位是字节。
返回值:
其他值:队列创建成功以后返回的队列句柄!
NULL:队列创建失败!
2.函数 xQueueCreateStatic()
此函数也是用于创建队列的,但是使用的静态方法创建队列,队列所需要的内存由用户自行分配,此函数本质上也是一个宏,此宏最终调用的是函数 xQueueGenericCreateStatic():
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t* pucQueueStorageBuffer,
StaticQueue_t* pxQueueBuffer)
uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
uxItemSize:队列中每个项目(消息)的长度,单位是字节。
pucQueueStorage:指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自行分配。此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等于(uxQueueLength* uxItemsSize)字节。
pxQueueBuffer:此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
返回值:
其他值:队列创建成功以后返回的队列句柄!
NULL:队列创建失败!
3.函数xQueueGenericCreate()
函数 xQueueGenericCreate() 用于动态创建队列,创建队列过程中需要的内存均通过 FreeRTOS中的动态内存管理函数 pvPortMalloc() 分配,函数原型如下:
QueueHandle_t xQueueGenericCreate(const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType)
uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
uxItemSize:队列中每个项目(消息)的长度,单位是字节。
ucQueueType:队列类型,由于 FreeRTOS 中的信号量等也是通过队列来实现的,创建信号量的函数最终也是使用此函数的,
因此在创建的时候需要指定此队列的用途,也就是队列类型,一共有六种类型:
queueQUEUE_TYPE_BASE 普通的消息队列
queueQUEUE_TYPE_SET 队列集
queueQUEUE_TYPE_MUTEX 互斥信号量
queueQUEUE_TYPE_COUNTING_SEMAPHORE 计数型信号量
queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号量
queueQUEUE_TYPE_RECURSIVE_MUTEX 递归互斥信号量
4.函数xQueueGenericCreateStatic()
此函数用于静态创建队列,创建队列过程中需要的内存需要由用户自行分配好,函数原型如下:
QueueHandle_t xQueueGenericCreateStatic(const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t* pucQueueStorage,
StaticQueue_t* pxStaticQueue,
const uint8_t ucQueueType)
uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
uxItemSize:队列中每个项目(消息)的长度,单位是字节。
pucQueueStorage:指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自行分配。
此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等于(uxQueueLength* uxItemsSize)字节。
pxStaticQueue:此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
ucQueueType:队列类型。
5.队列创建函数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;
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
if( uxItemSize == ( UBaseType_t ) 0 ) //第二个参数是每个队列项的大小,单位是字节
{
//队列项大小为 0,那么就不需要存储区。
xQueueSizeInBytes = ( size_t ) 0;
}
else //否则意味着队列项的大小不为 0
{ //分配足够的存储区,确保随时随地都可以保存所有的项目(消息)
//函数的参数uxQueueLength和uxItemSize指定了队列中最大队列项目(消息)数量和每个消息的长度,两者相乘就是消息存储区的大小。
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
} //动态分配长度为 uxQueueLength ,每个队列项的大小为 uxItemSize 的空间;
//调用函数pvPortMalloc()给队列分配内存,注意这里申请的内存大小是队列结构体和队列中消息存储区的总大小。
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
//动态开辟队列空间,开辟空间的大小为 队列结构体的大小加上队列中所需存储区的大小
//内存申请成功
if( pxNewQueue != NULL )
{
//计算出消息存储区的首地址,
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
//队列是使用动态方法创建的,所以队列字段 ucStaticallyAllocated 标
//记为 pdFALSE。
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif
//调用函数prvInitialiseNewQueue()初始化队列。
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, \
ucQueueType, pxNewQueue );
}
return pxNewQueue;
}
6.队列初始化函数
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, //队列长度
const UBaseType_t uxItemSize, //队列项目长度
uint8_t * pucQueueStorage, //队列项目存储区
const uint8_t ucQueueType, //队列类型
Queue_t * pxNewQueue ) //队列结构体
{
//防止编译器报错
( void ) ucQueueType;
if( uxItemSize == ( UBaseType_t ) 0 ) //如果队列项目长度为 0,
{
//队列项(消息)长度为 0,说明没有队列存储区,这里将 pcHead 指向队列开始地址
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
} //队列头指针指向队列开始地址
else //否则意味着队列项目不为 0
{
//设置 pcHead 指向队列项存储区首地址
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
} //队列头指针指向队列存储区首地址
//初始化队列结构体相关成员变量
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
//调用函数xQueueGenericReset()复位队列
( 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 );
}
7.队列复位函数
队列初始化函数prvInitialiseNewQueue()中调用了函数xQueueGenericReset()来复位队列, 函数xQueueGenericReset()代码如下:
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
taskENTER_CRITICAL(); //进入临界区
{
//初始化队列相关成员变量
pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->\
uxItemSize );
//pcTail 指针指向队列尾部的下一个位置,通过计算队列长度和每个元素的大小得到
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
//uxMessagesWaiting 表示队列的消息数量,初始化为 0;
pxQueue->pcWriteTo = pxQueue->pcHead;
//pcWriteTo 指向队列的头部,表示写操作时的起始位置;
pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - \
( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
//pcReadFrom 指向队列的尾部,表示读操作时的起始位置;
pxQueue->cRxLock = queueUNLOCKED;
pxQueue->cTxLock = queueUNLOCKED;
//cRxLock 和 cTxLock 是用于实现队列的读写锁。
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();
return pdPASS;
}
假如创建一个有4个队列项,每个队列项长度为32个字节的队列TestQueue,创建成功的队列如下图所示:
队列头指针 pcHead 指向队列项目一,表示队列的开始项;队列尾指针 pcTail 指向队列项目四,表示队列的结束项;pcWriteTo 指向队列的头部,表示写操作时的起始位置;pcReadFrom 指向队列的尾部,表示读操作的起始位置。uxMessagesWaiting 表示队列中的消息数量,初始时为 0;uxLength 表示队列的长度,此队列的长度为 4,分别是队列项目一、二、三、四;uxItemSize 表示队列项的长度,设置为 32 ,表示每项长度为 32 个字节;cRxLock = queueUNLOCKED 表示队列的读锁是未加锁状态,也就是说可以进行读操作;cTxLock = queueUNLOCKED 表示队列的写锁是未加锁状态,也就是说可以进行写操作;
8.向队列发送消息
FreeRTOS提供了8个向队列发送消息的API函数,如下表所示:
1.函数 xQueueSend()、xQueueSendToBack() 和 xQueueSendToFront()
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
const void* pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄
pvItemToQueue: 指向要发送的消息,发送时会将这个消息拷贝到队列中。
xTicksToWait: 阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大时间。
如果为 0 的话当队列满的时候就立即返回;当为 portMAX_DELAY 的话就会一直等待
,直到队列有空闲的队列项,也就是死等,但是宏 INCLUDE_vTaskSuspend 必须为 1.
返回值:
pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
2.函数xQueueOverwrite()
此函数也是用于向队列发送数据的,当队列满了以后会覆写掉旧的数据,不管这个旧数据有没有被其他任务或中断取走。这个函数常用于向那些长度为1的队列发送消息,此函数也是一个宏,最终调用的也是函数xQueueGenericSend(),函数原型如下:
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
const void* pvItemToQueue);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue: 指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
返回值:
pdPASS: 向队列发送消息成功与否,此函数只会返回 pdPASS!因为此函数执行过程中不在乎队列满不满,满了的话就覆盖掉旧的数据,总之发送数据肯定能成功!
3.函数xQueueSendFromISR()、函数xQueueSendToBackFromISR()和函数 xQueueSendToFrontFromISR()
这三个函数也是向队列中发送消息的,这三个函数用于中断服务函数中。这三个函数本质也是宏,其中函数 xQueueSendFromISR() 和 xQueueSendToBackFromISR() 是一样的,都是后向入队,也就是新的消息插入到队列的后面。函数 xQueueSendToFrontFromISR() 是前向入队,也就是将新的消息插入到队列的前面。这三个函数同样调用一个函数 xQueueGenericSendFromISR() 。这三个函数的函数原型如下:
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后就会返回此队列的队列句柄
pvItemToQueue: 指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置
用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值
为 pdTURE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdTURE: 向队列中发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
4.函数 xQueueOverwriteFromISR()
此函数是 xQueueOverwrite() 的中断级版本,用在中断服务函数中,在队列满的时候自动覆写掉旧的数据,此函数也是一个宏,实际调用的也是函数 xQueueGneericSendFromISR(),此函数原型如下:
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后就会返回此队列的队列句柄
pvItemToQueue: 指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置
用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值
为 pdTURE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 向队列发送消息成功与否,此函数只会返回 pdPASS!因为此函数执行过程中不在乎队列满不满,满了的话就覆盖掉旧的数据,总之发送数据肯定能成功!
9.队列上锁和解锁
队列的上锁和解锁是两个 API 函数:prvLockQueue() 和 prvUnlockQueue() :
上锁函数:
#define prvLockQueue( pxQueue )
taskENTER_CRITICAL();
{
if( ( pxQueue )->cRxLock == queueUNLOCKED )
{
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;
}
if( ( pxQueue )->cTxLock == queueUNLOCKED )
{
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;
}
}
taskEXIT_CRITICAL()
prvLockQueue() 函数就是将队列中的成员变量 cRxLock 和 cTxLock 设置为
queueLOCKED_UNMODIFIED 就可以;
解锁函数:
static void prvUnlockQueue( Queue_t * const pxQueue )
{
//上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间,入队或出队的数量,当队列
//上锁以后队列项是可以加入或者移除队列的,但是相应的列表不会更新。
taskENTER_CRITICAL();
{
//处理 cTxLock。
int8_t cTxLock = pxQueue->cTxLock;
while( cTxLock > queueLOCKED_UNMODIFIED )
{
/**************************************************************************/
/**************************省略掉与队列集相关代码**************************/
/**************************************************************************/
{
//将任务从事件列表中移除
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \
pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\
xTasksWaitingToReceive ) ) != pdFALSE )
{
//从列表中移除的任务优先级比当前任务的优先级高,因此要
//进行任务切换。
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
--cTxLock;
}
pxQueue->cTxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
//处理 cRxLock。
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
while( cRxLock > queueLOCKED_UNMODIFIED )
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) !=\
pdFALSE )
{
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cRxLock;
}
else
{
break;
}
}
pxQueue->cRxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
}
10.从队列读取消息
FreeRTOS中出队函数如下表所示:
1.函数xQueueReceive()
此函数用于在任务中从队列读取一条(请求)消息,读取成功以后就会将队列中的这条数据删除,此函数的本质是一个宏,真正执行的函数是 xQueueGneericReceive() 。此函数在读消息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void * pvBuffer,
TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中
xTicksToWait: 阻塞时间。此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。
如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY 的话就会一直等待
,直到队列有数据,也就是死等,但是宏 INCLUDE_vTaskSuspend 必须为 1.
返回值:
pdTRUE: 从队列中读取数据成功
pdFALSE: 从队列中读取数据失败
2.函数 xQueuePeek()
此函数用于在任务中从队列读取一条(请求)消息,读取成功以后不会将队列中的这条数据删除,此函数的本质是一个宏,真正执行的函数是 xQueueGneericReceive() 。此函数在读消息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:
BaseType_t xQueuePeek(QueueHandle_t xQueue,
void * pvBuffer,
TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中
xTicksToWait: 阻塞时间。此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。
如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY 的话就会一直等待
,直到队列有数据,也就是死等,但是宏 INCLUDE_vTaskSuspend 必须为 1.
返回值:
pdTRUE: 从队列中读取数据成功
pdFALSE: 从队列中读取数据失败
3.函数 xQueueReceiveFromISR()
此函数是 xQueueReceive() 的中断版本,用于在中断服务函数中从队列中读取(请求)一条消息,读取成功以后就会将队列中的这条数据删除。此函数在读取消息的时候是采用拷贝方式的, 所以需要用户提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
void* pvBuffer,
BaseType_t * pxTaskWoken);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓存区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中
pxTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值是由函数来设置的,用户不用进行
设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出
中断服务函数之前一定要进行一次任务切换。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。
4.函数 xQueuePeekFromISR()
此函数是 xQueuePeek() 的中断版本,此函数在读取成功以后不会将消息删除,此函数的原型为:
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
void * pvBuffer)
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。