FreeRTOS的学习(一)——STM32上的移植问题
FreeRTOS的学习(二)——任务优先级问题
FreeRTOS的学习(三)——中断机制
FreeRTOS的学习(四)——列表
FreeRTOS的学习(五)——系统延时
FreeRTOS的学习(六)——系统时钟
FreeRTOS的学习(七)——1.队列概念
FreeRTOS的学习(七)——2.队列入队源码分析
FreeRTOS的学习(七)——3.队列出队源码分析
FreeRTOS的学习(八)——1.二值信号量
FreeRTOS的学习(八)——2.计数型信号量
FreeRTOS的学习(八)——3.优先级翻转问题
FreeRTOS的学习(八)——4.互斥信号量
FreeRTOS的学习(九)——软件定时器
FreeRTOS的学习(十)——事件标志组
FreeRTOS的学习(十一)——任务通知
队列在FreeRTOS中起到比较重要的作用,主要用于任务之间消息的传递,取代了裸机时代中的全局变量交互功能。队列的机制实现了任务与任务、任务与中断之间的消息传递。
新版的队列结构体与旧版的结构体的表达方式存在一些区别,也多了一些内容,主要表现在:利用QueuePointers_t(队列指针结构体)和SemaphoreData_t(信号量结构体)这两个结构体分别去包含了如下成员。
typedef struct QueuePointers
{
//指向队列存储区最后一个字节
int8_t * pcTail;
//当用作队列的时候指向最后一个出队的队列项首地址
int8_t * pcReadFrom;
} QueuePointers_t;
typedef struct SemaphoreData
{
//持有互斥量的任务的句柄。
TaskHandle_t xMutexHolder;
//当用作递归互斥量的时候用来记录递归互斥量被调用的次数。
UBaseType_t uxRecursiveCallCount;
} SemaphoreData_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;
//当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED
volatile int8_t cRxLock;
//当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED
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;
队列作用实质上就是对于变量的接收和发送,因此,本文对于代码的解析主要针对通用的接收和发送函数,更多的队列用的解析将会在后面的文章中给出。另外值得说明的是,接收和发送程序也分为任务使用和中断使用的。中断使用的话会多一些现场保护等的中断专用功能。
另外发送函数可以发送到队列的最前面,最后面或者直接覆写(此功能常用于向那些长度为 1 的队列发送消息)队列中的值。
//任务级发送
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
//默认就是xQueueSendBack,也就是入队后是放在最后面的
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueOverwrite( xQueue, pvItemToQueue ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )
//中断级发送
#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) \
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
#define xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) \
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
#define xQueueOverwriteFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) \
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueOVERWRITE )
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) \
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
//任务级发送
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
//从队列中读取消息,并且读取之后不删除队列项
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
//中断级接收
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
void * const pvBuffer,
BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue,
void * const pvBuffer ) PRIVILEGED_FUNCTION;
队列所有的发送函数,包括前向入队、后向入队以及覆写都是基于任务级通用发送(入队)函数(xQueueGenericSend)实现的。
首先函数的参数如下:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
其中,
xQueue:队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue:指向要发送的信息,发送的过程中会将这个消息拷贝到队列中。
xTicksToWait:阻塞时间。
xCopyPosition:入队方式分为
queueSEND_TO_BACK:后向入队
queueSEND_TO_FRONT:前向入队
queueOVERWRITE:覆写入队。
通过选择xCopyPosition的不同数值实现不同的入队方式。
返回值:
pdPASS,向队列发送消息成功!
errQUEUE_FULL,队列已满,消息发送失败。
接下来开始分析入队函数的具体过程,下面给出基本的代码(省略冗余的东西,默认内存采用动态方式):
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;
for( ; ; )
{
taskENTER_CRITICAL();
{
//检查一下队列是不是满的,如果是满的话肯定不能发送的。当队列未满或者是覆写入队的话就可以将消息入队了
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
//如果队列未满,或者属于覆写功能
{ /*省略语句内函数*/ }
else
//队列已满且不是覆写功能
{ /*省略语句内函数*/ }
}
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 );
/* Resuming the scheduler will move tasks from the pending
* ready list into the ready list - so it is feasible that this
* task is already in the ready list before it yields - in which
* case the yield will not cause a context switch unless there
* is also a higher priority task in the pending ready list. */
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 */
}
根据上面的函数可以发现,其主要流程如下:
接下来给出省略的判断语句内部函数如下:
//查询队列现在是否还有剩余存储空间,如果采用覆写方式入队的话那就不用在乎队列是不是满的。
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
#if ( configUSE_QUEUE_SETS == 1 )
{
/* 省略掉与队列集相关代码 */
/* 讲解时默认不用队列集的功能 */
}
#else /* configUSE_QUEUE_SETS */
{
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* If there was a task waiting for data to arrive on the
* queue then unblock it now. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The unblocked task has a priority higher than
* our own so yield immediately. Yes it is ok to do
* this from within the critical section - the kernel
* takes care of that. */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xYieldRequired != pdFALSE )
{
/* This path is a special case that will only get
* executed if the task was holding multiple mutexes and
* the mutexes were given back in an order that is
* different to that in which they were taken. */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_QUEUE_SETS */
taskEXIT_CRITICAL();
return pdPASS;
}
else
{
/* 与上面的if语句,没有队列集的功能一致 */
}
}
在临界区中,进行了数据入队的操作,其中关于队列集的内容还没有接触,暂时不谈了,大概功能应该就是整合多个队列为一个集合吧,意义不是特别大目前来看。
具体步骤如下:
在上面讲解任务级通用入队函数时提到了队列的上锁和解锁,队列的上锁和解锁是两个API 函数:prvLockQueue和 prvUnlockQueue。
首先来看一下队列上锁函数 prvLockQueue
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL()
上锁的过程实际上就是设置队列的成员变量,判断发送和接收的锁是否上好了,上锁之后,队列项是可以从队列中加入或者移除的,但是相应的列表不会更新。
接下来看一下解锁的过程(省略队列集相关的函数):
static void prvUnlockQueue( Queue_t * const pxQueue )
{
/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. */
//上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间,入队或出队的数量,当队列
//上锁以后队列项是可以从队列中加入或者移除的,但是相应的列表不会更新。
taskENTER_CRITICAL();
{
int8_t cTxLock = pxQueue->cTxLock;
while( cTxLock > queueLOCKED_UNMODIFIED )
{
#if ( configUSE_QUEUE_SETS == 1 )
{
/* 省略掉与队列集相关代码 */
/* 讲解时默认不用队列集的功能 */
}
#else /* configUSE_QUEUE_SETS */
{
}
#endif /* configUSE_QUEUE_SETS */
--cTxLock;
}
pxQueue->cTxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
/* Do the same for the Rx lock. */
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();
}
队列这部分的东西是在有点多,最近精力也不如之前,目前先发一部分入队的内容……