FreeRtos源码分析之任务挂起和恢复(八)

一、概述

任务的挂起和进入临界区的功能类似,进入临界区通过直接操作硬件寄存器屏蔽中断来禁止任务切换,任务的挂起则是通过纯软件的方式来实现禁止任务切换的功能。

二、任务挂起和恢复的原理

FreeRtos使用uxSchedulerSuspended变量来表示内核调度器的打开和关闭,当这个值大于0时,内核调度器打开,等于0时内核调度器关闭。我们可以在FreeRtos的Systick中断xTaskIncrementTick中比较清晰的理解这个变量的含义。xTaskIncrementTick函数的部分代码如下:

BaseType_t xTaskIncrementTick( void )
{
     
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
     
        //此处省略执行任务调度部分代码
    }
    else
    {
     
        ++xPendedTicks;
         * scheduler is locked. */
        #if ( configUSE_TICK_HOOK == 1 )
            {
     
                vApplicationTickHook();
            }
        #endif
    }
    return xSwitchRequired;
}

接下来我们看下vTaskSuspendAll函数,它的作用是挂起所有任务(挂起任务调度器),其源码如下:

void vTaskSuspendAll( void )
{
     
    portSOFTWARE_BARRIER();

    ++uxSchedulerSuspended;

    portMEMORY_BARRIER();
}

portSOFTWARE_BARRIER和 portMEMORY_BARRIER默认定义为空,所以vTaskSuspendAll其实就是将uxSchedulerSuspended的值加一,只要这个值非0,那么Systick定时器中断就不会进行任务切换。
接下来我们分析下任务恢复的代码:

BaseType_t xTaskResumeAll( void )
{
     
    TCB_t * pxTCB = NULL;
    BaseType_t xAlreadyYielded = pdFALSE;

    /* 保证之前有调用过任务挂起函数. */
    configASSERT( uxSchedulerSuspended );

    /* It is possible that an ISR caused a task to be removed from an event
     * list while the scheduler was suspended.  If this was the case then the
     * removed task will have been added to the xPendingReadyList.  Once the
     * scheduler has been resumed it is safe to move all the pending ready
     * tasks from this list into their appropriate ready list. */
    /* 当调度器被挂起之后,中断可能会导致一个任务从event list中移除。这种情况下被移除的任务会被添加到xPendingReadyList链表中。
     * 一旦调度器重新恢复,可以安全的把所有挂起状态的就绪任务添加到离它们最近的就绪态链表。*/
    taskENTER_CRITICAL();
    {
     
        --uxSchedulerSuspended;

        if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
        {
     
            if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )//如果当前任务总数大于0
            {
     
                /* Move any readied tasks from the pending list into the
                 * appropriate ready list. */
                /*将就绪任务从挂起链表中删除,放到最近的就绪链表中。*/
                while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
                {
     
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
                    ( void ) uxListRemove( &( pxTCB->xEventListItem ) );//将任务从事件链表中删除
                    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );//将任务从对应的状态链表中删除,这里应该是挂起状态链表
                    prvAddTaskToReadyList( pxTCB );//将任务添加到就绪态任务链表

                    /* If the moved task has a priority higher than the current
                     * task then a yield must be performed. */
                    /*如果移动的任务优先级高于当前任务,则必须进行一次任务调度。*/
                    if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                    {
     
                        xYieldPending = pdTRUE;
                    }
                    else
                    {
     
                        mtCOVERAGE_TEST_MARKER();
                    }
                }

                if( pxTCB != NULL )
                {
     
                    /* A task was unblocked while the scheduler was suspended,
                     * which may have prevented the next unblock time from being
                     * re-calculated, in which case re-calculate it now.  Mainly
                     * important for low power tickless implementations, where
                     * this can prevent an unnecessary exit from low power
                     * state. */
                    /* 任务在调度器被挂起时会解除阻塞状态,这会阻止下一个阻塞时间被重新计算,因此需要重新计算任务的阻塞时间。
                     * 这对于无空循环低功耗的实现非常重要,可以防止不必要的退出低功耗状态。*/
                    prvResetNextTaskUnblockTime();
                }

                /* If any ticks occurred while the scheduler was suspended then
                 * they should be processed now.  This ensures the tick count does
                 * not  slip, and that any delayed tasks are resumed at the correct
                 * time. */
                 /**这段代码可以保证计数器没有丢失计数,并且任何延时任务都可以在正确的时间被恢复运行。*/
                {
     
                    TickType_t xPendedCounts = xPendedTicks; /* Non-volatile copy. */

                    if( xPendedCounts > ( TickType_t ) 0U )
                    {
     
                        do
                        {
     
                            if( xTaskIncrementTick() != pdFALSE )
                            {
     
                                xYieldPending = pdTRUE;
                            }
                            else
                            {
     
                                mtCOVERAGE_TEST_MARKER();
                            }

                            --xPendedCounts;
                        } while( xPendedCounts > ( TickType_t ) 0U );

                        xPendedTicks = 0;
                    }
                    else
                    {
     
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                //如果使用了抢占式任务调度,则进行一次任务切换
                if( xYieldPending != pdFALSE )
                {
     
                    #if ( configUSE_PREEMPTION != 0 )
                        {
     
                            xAlreadyYielded = pdTRUE;
                        }
                    #endif
                    taskYIELD_IF_USING_PREEMPTION();
                }
                else
                {
     
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        else
        {
     
            mtCOVERAGE_TEST_MARKER();
        }
    }
    taskEXIT_CRITICAL();

    return xAlreadyYielded;
}

任务恢复主要做了如下内容:

  • [1] 当调度器被挂起之后,中断可能会导致一个任务从事件列表event list中移除。这种情况下被移除的任务会被添加到xPendingReadyList链表中。一旦调度器重新恢复,需要把所有挂起状态的就绪任务添加到离它们最近的就绪态链表。
  • [2] 重新计算下一个阻塞任务的阻塞时间,防止退出低功耗状态。
  • [3] 重新计算SysTick的时钟节拍。因为当任务挂起后,系统的时钟节拍不再计数,取而代之的是一个名为xPendedTicks的变量,当系统从挂起态恢复时,需要重新计算系统的时钟节拍数,并且系统挂起的时间也要算到延时任务的延时时间中去。比如下一个延时任务的延时时间为10ms,如果任务调度器挂起了2ms,那么调度器恢复之后,延时任务的剩余延时时间应该是8ms。简言之,任务挂起后任务虽然不运行,但是在任务恢复之后,挂起消耗的这段时间绝对不能被忽略。
  • [4] 如果从xPendingReadyList
    恢复为就绪态任务的优先级高于当前任务,则进行一次任务切换。

FreeRtos带中文注释源码Gitee地址:https://gitee.com/zBlackShadow/FreeRtos10.4.3.git

你可能感兴趣的:(FreeRtos,FreeRtos,操作系统)