一.前言
本文旨在深入了解freeRTOS。读者阅读的时候,可以简略阅读一下介绍部分还有代码的注释,接着有不明白的api函数可以参考freeRTOS网站上的文档。
二.介绍部分
对于接收队列xQueueReceive函数而言,一共分为三个大类:1.刚开始的赋值,判断操作。2.进入临界区域的部分,3.退出临界区,判断有没有超时的部分。
1. 首先,对于初始化部分,里面最主要的是一些赋值,像是const修饰等等提高代码安全性和可移植性。
2. 接下来来到了函数中最主要的循环部分,为了避免队列被其他程序或者中断打断,此处使用了一个进入临界区的办法,紧接着获取到队列中非空队列项的数量。进入判断。
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 ) );
/* 只有当数据大小为零时,接收数据的缓冲区才能为 NULL(因此不会有数据被复制到缓冲区) */
configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );
/* 当调度器挂起时,任务无法阻塞等待队列数据。. */
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/*这个函数在一定程度上放宽了编码标准,允许在函数内部使用多个 return 语句。这样做是为了提高执行时间效率 */
for( ;; )
{
taskENTER_CRITICAL(); //进入临界区
{
/*uxMessagesWaiting 记录着队列里非空队列项的数量。*/
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
3.如果队列里面有数据,就进行数据的拷贝。然后再判断队列是否有空间,如果有就给因为队列已满发送不了的任务转为运行态。
/* 现在队列中有数据吗?要运行调用任务,它必须是想要访问队列的最高优先级任务。*/
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;
}
4.当队列没有数据的部分
else
{
//判断接受队列是否设置了等待时间
if( xTicksToWait == ( TickType_t ) 0 )
{
/*没设置等待时间就退出*/
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* 队列为空,但指定了阻塞时间,所以要配置超时结构体。 */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
5.设置完超时时间以后,退出临界区,当前任务给队列上锁并进入到判断程序是否超时。
6.当程序不超时的时候,如果队列为空,则将任务挂起,你可能会问,如果任务被挂起了,那这个接收函数不就再也执行不了了吗,不用担心,因为在xQueueSend函数中会有类似xQueueReceive 第二部分的一个操作,即如果队列有数据,则将相应的等待任务项解放出来。
/*现在临界区已经退出,中断和其他任务可以向队列发送数据,也可以从队列接收数据了。 */
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();
}
}
7. 超时的时候,如果队列没数据就退出,有数据就重新接受数据。
else
{
/* 超时的时候如果队列中没有数据就退出,否则就回到循环开始处并尝试读取数据。 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
/*超时无数据,就返回标记值*/
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
/*超时的时候有数据就标记,然后回到最初的循环点*/
mtCOVERAGE_TEST_MARKER();
}
}
} /*lint -restore 对应外面的for循环 */
} /*xQueueRecive函数*/
————————————————
版权声明:本文为CSDN博主「Gary Studio」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_73991989/article/details/146277599
|
|