[经验分享] FreeRTOS 源码分析之接收队列篇

[复制链接]
97|0
Xiashiqi 发表于 2025-11-6 12:07 | 显示全部楼层 |阅读模式
一.前言      
         本文旨在深入了解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

您需要登录后才可以回帖 登录 | 注册

本版积分规则

105

主题

310

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部