FreeRTOS的队列是用于任务与任务、任务与中断之间通信的一种数据结构。各个任务之间的数据通信通过一段共同的存储空间按需获取和发送数据,这段存储空间采用队列的形式进行访问,可以采用动态或静态的方式来创建这段内存。任务往队列中发送数据和接收数据通常采用先进先出(FIFO)的存储缓冲机制,当然也可以使用后进先出(LIFO)的形式。FreeRTOS往队列发送和接收数据采用的是直接拷贝的形式,将要发送和接收的数据使用memcpy这个函数拷贝到相应的内存空间,这样做的好处就是如果传递的是一个变量,那这个变量放入队列后,变量就可以作为它用重新赋值给其它代码使用,因为数据已经保存在了队列中,这种方式叫值传递。当然也可以传递一段固定存储空间的内存指针给队列做保存,但必须保证这段空间不能修改,否则在接收端根据收到的指针去访问那段内存的话就可能不是自己想要的数据了。
入队和出队:
入队就是往队列中发送,往队列中发送数据分三种情况。第一种如果在往队列发送数据的时候没有设置阻塞时间,则无论队列有没有满,入队成功与否,都毫不留情直接返回。第二种往队列发送数据的时候设置一段阻塞时间,如果队列没满则直接将数据复制到队列中,队列满了的话就将任务挂到等待列表进入阻塞状态,当阻塞时间到了就退出阻塞状态立即返回往下执行。第三种往队列发送数据时将阻塞时间设置为最大值,队列没满则直接将数据复制到队列中,队列满了的话就一直阻塞,直到入队成功为止。
队里的使用:
一般在使用队列前需要先创建队列,然后在某个线程中就可以往队列中发送信息,在另一个线程中从队列中获取信息,以一个简单的例子来带入:
/* 定义队列变量 */
QueueHandle_t Led_Queue;
/* 用于创建LED任务的开始任务 */
void start_task(void *pvParameters)
{
/* 进入临界区 */
taskENTER_CRITICAL();
/* 创建消息Led_Queue */
Led_Queue = xQueueCreate(1, sizeof(unsigned char));
/* 创建LED0任务 */
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
/* 创建LED1任务 */
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
/* 删除开始任务 */
vTaskDelete(StartTask_Handler);
/* 退出临界区 */
taskEXIT_CRITICAL();
}
/* LED0任务函数 */
void led0_task(void *pvParameters)
{
unsigned char led_message = 0;
while(1)
{
LED0=~LED0;
led_message =! led_message;
/* 发送消息到队列中 */
xQueueSend(Led_Queue, &led_message, 10);
vTaskDelay(500);
}
}
/* LED1任务函数 */
void led1_task(void *pvParameters)
{
unsigned char led_message = 0;
while(1)
{
/* 从队列中获取消息 */
xQueueReceive(Led_Queue, &led_message, 10);
if(led_message == 1){
LED1 = 0;
}
else{
LED1 = 1;
}
vTaskDelay(10);
}
}
根据上面这段应用,可以了解到第一个使用到的函数就是队列创建,队列创建后会返回一个队列指针,这个队列指针是个结构体指针变量(QueueDefinition *):
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{...}
typedef struct QueueDefinition * QueueHandle_t;
结构体变量(QueueDefinition )保存着队列的描述信息,每个队列都对应一个结构体变量,可以通过这个结构体获取到队列当前的状态、信息及空间等。结构体的定义及含义如下:
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
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; /* 当队列上锁后用于统计队列入队的列表项数量 */
/* 如果使用了静态存储的话把这个变量置为pdTURE,确保删除时使用静态方式 */
#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;
队列的创建(xQueueCreate)实际上是一个宏,最终调用的是 xQueueGenericCreate:
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
其中有个参数用于指定创建的队列类型“ queueQUEUE_TYPE_BASE ” ,实际上队列的创建还支持多种类型:
#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 ) /* 递归互斥信号量 */
任务创建函数的源码分析如下:
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
size_t xQueueSizeInBytes;
uint8_t *pucQueueStorage;
/* 队列的长度必须大于0(即队列项大于0) */
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
if( uxItemSize == ( UBaseType_t ) 0 )
{
/* 队列项的字节数为0,即不需要存储区 */
xQueueSizeInBytes = ( size_t ) 0;
}
else
{
/* 队列项总大小 = 队列项数量 * 队列项字节数 */
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
}
/* 动态分配队列所需的总内存(包含消息存储区),Queue_t为队列结构体信息的内存空间 */
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
if( pxNewQueue != NULL )
{
/* 内存分配成功,保存队列内存块的指针 */
pucQueueStorage = ( uint8_t * ) pxNewQueue;
/* 队列指针指向存储区(不包含消息存储区) */
pucQueueStorage += sizeof( Queue_t );
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* 队列可以静态和动态创建的情况下默认为动态创建,这里标记不使用静态分配,后面删除时会采用动态形式 */
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif
/* 初始化队列 队列项目数 , 每个项目字节数, 队列存储区, 队列类型, 队列头部 */
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}
else
{
/* 队列分配失败 */
traceQUEUE_CREATE_FAILED( ucQueueType );
mtCOVERAGE_TEST_MARKER();
}
/* 返回队列指针 */
return pxNewQueue;
}
#endif
在分配好内存后就会去初始化队列,初始化队列的源码分析:
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 )
{
/* 因为分配的队列存储区为空,但是pcHead不能定义为NULL,将pcHead定义为队列信息区 */
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
/* 队列存储区不为空,定义pcHead指向队列存储区 */
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
/* 定义队列大小信息 */
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
/* 初始化队列其它信息 */
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
/* 跟踪调试相关字段初始化 */
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewQueue->ucQueueType = ucQueueType;
}
#endif
/* 队列集相关字段初始化 */
#if( configUSE_QUEUE_SETS == 1 )
{
pxNewQueue->pxQueueSetContainer = NULL;
}
#endif
traceQUEUE_CREATE( pxNewQueue );
}
其中调用接口 xQueueGenericReset 初始化队列其它信息的源码分析如下:
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = xQueue;
configASSERT( pxQueue );
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 初始化队列成员信息,代表的含义参考前面结构体定义的注释 */
pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
pxQueue->pcWriteTo = pxQueue->pcHead;
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize );
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();
/* 返回成功 */
return pdPASS;
}
上面这个函数使用动态内存来创建任务的,当然也有静态分配的方式,需要用户自行分配内存空间,将内存的指针传递进去,他的定义如下:
xQueueCreateStatic(uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer)
#define xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer ) xQueueGenericCreateStatic( ( uxQueueLength ), ( uxItemSize ), ( pucQueueStorage ), ( pxQueueBuffer ), ( queueQUEUE_TYPE_BASE ) )
这个函数的实现和上面 xQueueGeneric 几乎是一样的,除了一些队列参数的设置上有差异,这段代码有兴趣的话自己看一下就好了。
在队列创建完后就可以对队列进行操作了,向队列发送数据、从队列获取数据。先分析一下往队列发送数据的源码,往队列发送数据在FreeRTOS中有很多个接口函数,一种用于任务间的通信, 一种用于中断服务函数,包括的接口函数有8个,列举如下:
xQueueSend(xQueue, pvItemToQueue, xTicksToWait); /* 默认方式,发送消息到队列尾部 */
xQueueSendToBack(xQueue, pvItemToQueue, xTicksToWait); /* 发送消息到队列尾部 */
xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait);/* 发送消息到队列头部 */
xQueueOverwrite(xQueue, pvItemToQueue); /* 队列满后以覆盖的方式写入队列,旧的项目会被新的项目覆盖 */
xQueueSendFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken); /* 在中断服务函数中向队列尾部发送消息 */
xQueueSendToBackFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken); /* 在中断服务函数中向队列尾部发送消息 */
xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken);/* 在中断服务函数中向队列头部发送消息 */
xQueueOverwriteFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken); /* 在中断服务函数中以覆盖的方式写入队列 */
前面四个任务级入队函数都是调用的同一个接口往队列写入数据,只是形参上会指明使用哪种方式来写入
#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 )
这里先分析一下入队函数的源码:
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 ) ) );
configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
for( ;; )
{
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 当前队列内存空间还有剩或者以复写方式写入 */
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
#if ( configUSE_QUEUE_SETS == 1 )
{
/* 省略掉与队列集相关代码 */
}
#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
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 返回pdPASS,标记入队成功 */
return pdPASS;
}
else /* 队列满了执行这个分支 */
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 队列满了,并且设置阻塞时间为0则退出临界区返回队列已满 */
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
/* 超时时间标志还没设置 */
else if( xEntryTimeSet == pdFALSE )
{
/* 队列满了,形参设置了阻塞时间则记录调用这个API时的系统时钟计数值 */
vTaskInternalSetTimeOutState( &xTimeOut );
/* 设置超时时间标志位 */
xEntryTimeSet = pdTRUE;
}
/* 超时时间标志设置过了则直接往下执行 */
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 调度器休眠,执行到这里说明队列满了,且阻塞时间不为0 */
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;
}
}
}
中断里面往队列写数据都是调用的中断级入队函数,如下:
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
#define xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
#define xQueueOverwriteFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueOVERWRITE )
xQueueGenericSendFromISR 中断级通用入队函数和前面的操作基本一致,在进去临界区和队列上锁和解锁上的一些操作略有不同,源码分析如下:
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = xQueue;
configASSERT( pxQueue );
configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
/* 进入中断临界区 */
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
/* 队列未满或者以覆写的方式写入 */
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
/* 读取变量cTxLock,用于判断队列是否上锁 */
const int8_t cTxLock = pxQueue->cTxLock;
traceQUEUE_SEND_FROM_ISR( pxQueue );
/* 复制数据到队列中 */
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* 队列处于解锁状态的话执行这个分支 */
if( cTxLock == queueUNLOCKED )
{
#if ( configUSE_QUEUE_SETS == 1 )
{
/* 省略掉与队列集相关代码 */
}
#else
{
/* 任务等待接收列表不为空,说明有任务在请求消息时被阻塞了 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
/* 将任务从等待接收列表中移除 */
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 移除的任务比当前任务的优先级高 */
if( pxHigherPriorityTaskWoken != NULL )
{
/* 标记退出中断后需要进行一次任务切换 */
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
/* 队列上锁了执行这个分支 */
else
{
/* 如果队列上锁的话就将cTxLock加一,表示进行了一次入队操作,中断退出后在队列解锁时再做处理 */
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
}
xReturn = pdPASS;
}
else
{
/* 队列满了返回errQUEUE_FULL */
traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
xReturn = errQUEUE_FULL;
}
}
/* 退出中断临界区 */
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
下面依次分析一下入队函数中出现的一些接口是如何实现的,出现的接口函数有:
/* 将数据复制到队列中 */
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition );
/* 将任务从等待接收列表中删除 */
BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList );
/* 队列上锁 */
prvLockQueue( pxQueue );
/* 队列解锁 */
static void prvUnlockQueue( Queue_t * const pxQueue );
/* 检查超时时间是否到了 */
BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait );
/* 将任务插入到相应的事件列表中 */
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait );
还有几个接口只是简单的赋值操作,就不放分析了,先看下将数据复制到队列中是如何实现的:
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;
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
/* 互斥信号量(这个部分在信号量章节再做分析) */
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
pxQueue->u.xSemaphore.xMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
/* 后向入列 */
else if( xPosition == queueSEND_TO_BACK )
{
/* 将写入的数据复制到队列的下一个空闲内存 */
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
/* 更新队列空闲内存块的索引指针 */
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 );
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 )
{
/* 如果是覆写方式,消息的数量还要减一,后面代码执行了加一,就相当于消息数量不变 */
--uxMessagesWaiting;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 当前列表项数量加1 */
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
return xReturn;
}
往队列中发送数据后,可以查看下是否有任务因为等待读取队列的数据而导致阻塞,如果有就需要将任务从等待接收列表中删除了,使用 xTaskRemoveFromEventList 这个接口,源码分析如下:
BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList )
{
TCB_t *pxUnblockedTCB;
BaseType_t xReturn;
/* 获取事件列表首个列表项所属的任务控制块 */
pxUnblockedTCB = listGET_OWNER_OF_HEAD_ENTRY( pxEventList );
configASSERT( pxUnblockedTCB );
/* 从对应的事件列表中删除任务 */
( void ) uxListRemove( &( pxUnblockedTCB->xEventListItem ) );
/* 如果调度器目前正常运行 */
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* 将任务从状态列表中移除 */
( void ) uxListRemove( &( pxUnblockedTCB->xStateListItem ) );
/* 将任务添加到就绪列表 */
prvAddTaskToReadyList( pxUnblockedTCB );
}
else/* 调度器被挂起 */
{
/* 将任务插入到挂起就绪列表,直到调度器恢复再挂回就绪列表 */
vListInsertEnd( &( xPendingReadyList ), &( pxUnblockedTCB->xEventListItem ) );
}
/* 如果当前解除阻塞的任务比当前正在运行的任务优先级高则标记需要进行一次任务切换 */
if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority )
{
xReturn = pdTRUE;
xYieldPending = pdTRUE;
}
else
{
/* 暂时先不进行任务切换,等调度器自行从就绪列表中切换任务 */
xReturn = pdFALSE;
}
#if( configUSE_TICKLESS_IDLE != 0 )
{
/* 配置了相应的宏则复位下一个任务的解锁时间 */
prvResetNextTaskUnblockTime();
}
#endif
return xReturn;
}
入队函数在更新超时时间之前和之后分别使用了队列上锁和队列解锁函数,队列上锁源码如下:
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL()
队列解锁很简单,就是将队列结构体变量的 cRxLock 和 cTxLock 设置为 queueLOCKED_UNMODIFIED ,再看下队列解锁的源码分析:
static void prvUnlockQueue( Queue_t * const pxQueue )
{
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 获取队列被锁期间发送到队列的项目数量 */
int8_t cTxLock = pxQueue->cTxLock;
/* 队列上锁期间有数据添加到队列中(中断也可能会在任务操作队列的时候入队,cTxLock就会递增) */
while( cTxLock > queueLOCKED_UNMODIFIED )
{
#if ( configUSE_QUEUE_SETS == 1 )
{
/* 队列集相关代码 */
}
#else
{
/* 如果任务等待接收队列不为空 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
/* 将任务从等待接收列表中移除 */
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 标记需要进行一次任务切换 */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 等待接收队列为空则退出循环 */
else
{
break;
}
}
#endif
/* 有可能队列被上锁了多次,需要递减循环操作上面的步骤 */
--cTxLock;
}
/* 标记为解锁状态 */
pxQueue->cTxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
/* 上面队列解锁是针对任务等待接收列表的操作, 这里是等待发送列表的操作,代码大同小异就不分析了*/
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();
}
入队函数一旦设置了超时时间,如果队列是满的并且时间未到就会进入阻塞状态,具怎么判断超时时间的就要分析一下超时时间检查函数 xTaskCheckForTimeOut ,源码分析如下:
BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait )
{
BaseType_t xReturn;
configASSERT( pxTimeOut );
configASSERT( pxTicksToWait );
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 获取当前计数器时间 */
const TickType_t xConstTickCount = xTickCount;
/* 当前计数器时间-开始等待接收时的时间 = 已经等待的时间 */
const TickType_t xElapsedTime = xConstTickCount - pxTimeOut->xTimeOnEntering;
#if ( INCLUDE_vTaskSuspend == 1 )
if( *pxTicksToWait == portMAX_DELAY )
{
/* 等待的时间为最大值的话返回pdFALSE,一直阻塞永远不会超时 */
xReturn = pdFALSE;
}
else
#endif
if( ( xNumOfOverflows != pxTimeOut->xOverflowCount ) && ( xConstTickCount >= pxTimeOut->xTimeOnEntering ) )
{
/* 返回等待结束 */
xReturn = pdTRUE;
}
/* 等待时间还没到 */
else if( xElapsedTime < *pxTicksToWait )
{
/* 慢慢缩减等待时间 */
*pxTicksToWait -= xElapsedTime;
/* 更新超时结构体变量 */
vTaskInternalSetTimeOutState( pxTimeOut );
xReturn = pdFALSE;
}
/* 等待时间到了 */
else
{
/* 等待时间变为0 */
*pxTicksToWait = 0;
/* 返回等待结束 */
xReturn = pdTRUE;
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 返回结果 */
return xReturn;
}
超时时间还未到的话会将任务插入相应的事件列表和延时列表中,使用函数 vTaskPlaceOnEventList 来实现,源码分析如下:
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )
{
configASSERT( pxEventList );
/* 将任务插入到队列的事件列表中,前面列表章节已经分析过了 */
vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );
/* 将任务添加到延时列表中,前面时间管理章节已经分析过了 */
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
}
入队函数分析完了,接下来分析一下出队函数,出队函数接口有4个:
/* 从队列中读取消息,读取完后删除相应的队列项 */
xQueueReceive(QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait);
/* 从队列中读取消息,读取完后不删除相应的队列项 */
xQueuePeek(QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait);
/* 用于中断服务函数使用, 从队列中读取消息,读取完后删除相应的队列项 */
xQueueReceiveFromISR(QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken);
/* 用于中断服务函数使用, 从队列中读取消息,读取完后不删除相应的队列项 */
xQueuePeekFromISR(QueueHandle_t xQueue, void * const pvBuffer);
实际上出队函数分析完后,入队函数做的操作基本和出队大同小异,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;
configASSERT( ( pxQueue ) );
configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
for( ;; )
{
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 获取队列项目数 */
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* 如果队列中有项目 */
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* 将数据从队列中拷贝出来 */
prvCopyDataFromQueue( pxQueue, pvBuffer );
traceQUEUE_RECEIVE( pxQueue );
/* 队列数量递减 */
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/* 队列中没数据 */
{
/* 接收不到数据也不阻塞直接跳过 */
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 退出临界区 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
/* 返回队列为空 */
return errQUEUE_EMPTY;
}
/* 超时时间标志还没设置过 */
else if( xEntryTimeSet == pdFALSE )
{
/* 队列没数据,形参设置了阻塞时间则记录调用这个API时的系统时钟计数值 */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
/* 超时时间标志设置过了则直接往下执行 */
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 挂起调度器 */
vTaskSuspendAll();
/* 队列上锁 */
prvLockQueue( pxQueue );
/* 更新超时状态,延时时间还未到 */
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
{
/* 队列不为空直接解锁队列并恢复调度器,通过循环重试一次 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
/* 超时时间到了,在systick中断定时函数判断到等待列表中任务的等待时间到了后会执行到这里 */ */
else
{
/* 解锁队列,如果上锁期间有数据加入到队列中则清除等待接收列表,
如果优先级高,标记了任务切换,则在恢复调度器里面会进行任务切换 */
prvUnlockQueue( pxQueue );
/* 恢复调度器 */
( void ) xTaskResumeAll();
/* 队列为空 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
/* 超时时间到了队列依旧为空则返回错误 */
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
还有个任务级出队函数 xQueuePeek,这个函数和 xQueueReceive 几乎是一致的,针对出队后不删除队列数据做了些简单的标记操作。还有中断级出队函数 xQueueReceiveFromISR 和 xQueuePeekFromISR 针对在中断中从队列获取数据做的一些操作,经过前面任务级入队和中断级入队的分析,实际上这个操作过程和前面都是一样的,这里就不放出来分析了,免得章节太长。