freertos内核走读2——task任务调度机制(二)

本文为jorhai原创,转载请注明,谢谢!

前文介绍了任务的全局变量和任务上下文结构TCB。
本文继续介绍任务相关的操作函数。在相关的函数介绍中即保留了freertos原有的注释(英文注释部分),也添加了自己的理解(中文部分)。

任务的创建:
思考下,任务创建时,freertos都为我们做了什么?完成了哪些初始化工作?任务创建在哪里可以调用,创建的任务在什么时候可以运行?
定义:
相关的参数就不在介绍了。
#define xTaskCreate( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask ) xTaskGenericCreate( ( pvTaskCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask ), ( NULL ), ( NULL ) )

下面介绍xTaskGenericCreate原型:

BaseType_t xTaskGenericCreate( TaskFunction_t pxTaskCode, const char * const pcName, const uint16_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, StackType_t * const puxStackBuffer, const MemoryRegion_t * const xRegions ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
BaseType_t xReturn;
TCB_t * pxNewTCB;
StackType_t *pxTopOfStack;
/*一些断言,其中优先级不能超过系统配置*/
    configASSERT( pxTaskCode );
    configASSERT( ( ( uxPriority & ( UBaseType_t ) ( ~portPRIVILEGE_BIT ) ) < ( UBaseType_t ) configMAX_PRIORITIES ) );
/*分配栈内存和TCB数据内存,会按照处理栈向上生长或向下分配,分配时调用的pvPortMalloc 和 
pvPortMallocAligned ,然后将栈memset为0xa5, prvAllocateTCBAndStack 的具体操作可以自己查看,
这里不再列出*/
    /* Allocate the memory required by the TCB and stack for the new task,
    checking that the allocation was successful. */
    pxNewTCB = prvAllocateTCBAndStack( usStackDepth, puxStackBuffer );

    if( pxNewTCB != NULL )
    {/*分配成功*/
        /* portUSING_MPU_WRAPPERS 表示是否使用了MPU,这里就不在详细说明*/
        #if( portUSING_MPU_WRAPPERS == 1 )
            /* Should the task be created in privileged mode? */
            BaseType_t xRunPrivileged;
            if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
            {
                xRunPrivileged = pdTRUE;
            }
            else
            {
                xRunPrivileged = pdFALSE;
            }
            uxPriority &= ~portPRIVILEGE_BIT;

            if( puxStackBuffer != NULL )
            {
                /* The application provided its own stack.  Note this so no
                attempt is made to delete the stack should that task be
                deleted. */
                pxNewTCB->xUsingStaticallyAllocatedStack = pdTRUE;
            }
            else
            {
                /* The stack was allocated dynamically.  Note this so it can be
                deleted again if the task is deleted. */
                pxNewTCB->xUsingStaticallyAllocatedStack = pdFALSE;
            }
        #endif /* portUSING_MPU_WRAPPERS == 1 */

        /* Calculate the top of stack address.  This depends on whether the
        stack grows from high memory to low (as per the 80x86) or vice versa.
        portSTACK_GROWTH is used to make the result positive or negative as
        required by the port. */
        #if( portSTACK_GROWTH < 0 )
        {
            pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( uint16_t ) 1 );
            pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type. */

            /* Check the alignment of the calculated top of stack is correct. */
            configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
        }
        #else /* portSTACK_GROWTH */
        {
            pxTopOfStack = pxNewTCB->pxStack;

            /* Check the alignment of the stack buffer is correct. */
            configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

            /* If we want to use stack checking on architectures that use
            a positive stack growth direction then we also need to store the
            other extreme of the stack space. */
            pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( usStackDepth - 1 );
        }
        #endif /* portSTACK_GROWTH */

/*初始化TCB信息,将用户传递的参数保存到pxNewTCB 中,
对pxNewTCB 中的一些变量进行初始化(例如链表item初始化),这里不再单列*/
        /* Setup the newly allocated TCB with the initial state of the task. */
        prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth );

        /* Initialize the TCB stack to look as if the task was already running,
        but had been interrupted by the scheduler.  The return address is set
        to the start of the task function. Once the stack has been initialised
        the top of stack variable is updated. */
        #if( portUSING_MPU_WRAPPERS == 1 )
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
        }
        #else /* portUSING_MPU_WRAPPERS */
        {
            /*初始化上面分配的栈空间,同时返回栈初始地址,pxPortInitialiseStack 函数会在下面单独列出*/
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
        }
        #endif /* portUSING_MPU_WRAPPERS */
        /*是否将分配的tcb块返回到pxCreatedTask 中,这个是可选的,
        当指针位NULL时不返还任务的句柄,一般建议返回*/
        if( ( void * ) pxCreatedTask != NULL )
        {
            /* Pass the TCB out - in an anonymous way.  The calling function/
            task can use this as a handle to delete the task later if
            required.*/
            *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        /*进入中断临界区*/
        /* Ensure interrupts don't access the task lists while they are being
        updated. */
        taskENTER_CRITICAL();
        {
/*更新任务总数*/
            uxCurrentNumberOfTasks++;
            if( pxCurrentTCB == NULL )
            {/*如果pxCurrentTCB 为空,则表示当前创建的是第一个任务, 
            将创建的任务设置为pxCurrentTCB ——即当前运行任务*/
                /* There are no other tasks, or all the other tasks are in
                the suspended state - make this the current task. */
                pxCurrentTCB =  pxNewTCB;

                if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
                {
                    /* This is the first task to be created so do the preliminary
                    initialisation required.  We will not recover if this call
                    fails, but we will report the failure. */
                    /*创建的是第一个任务,将任务调度需要使用的全局list进行初始化: 
                    具体包括:pxReadyTasksLists ,xDelayedTaskList1,xDelayedTaskList2 ,
                    xPendingReadyList ,xTasksWaitingTermination ,xSuspendedTaskList */
                    prvInitialiseTaskLists();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {/*本次创建的任务明显不是第一个被创建的任务*/
                /* If the scheduler is not already running, make this task the
                current task if it is the highest priority task to be created
                so far. */
                if( xSchedulerRunning == pdFALSE )
                {/*如果调度器还没用运行,则比较新建的任务和运行任务的优先级, 
                如果新创建的任务优先级大,则设置pxCurrentTCB 为新建任务*/
                    if( pxCurrentTCB->uxPriority <= uxPriority )
                    {
                        pxCurrentTCB = pxNewTCB;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            /* uxTaskNumber 累加,并且为任务分配一个uxTCBNumber,即task id,主要在调试时使用 */
            uxTaskNumber++;

            #if ( configUSE_TRACE_FACILITY == 1 )
            {
                /* Add a counter into the TCB for tracing only. */
                pxNewTCB->uxTCBNumber = uxTaskNumber;
            }
            #endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
            /*将任务添加到ready list中,这个函数是将task的xGenericListItem添加到任务优先级的readylist中。 
            可见新建的任务总是处于就绪状态,等待调度器调度,并开始运行,直到任务本身某些操作而进入其他状态*/
            prvAddTaskToReadyList( pxNewTCB );

            xReturn = pdPASS;
            /*其他可以移植的对tcb的初始化操作,用户自己添加*/
            portSETUP_TCB( pxNewTCB );
        }
        /*退出临界区*/
        taskEXIT_CRITICAL();
    }
    else
    {/*如果上面没有分配到内存,返回失败*/
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
        traceTASK_CREATE_FAILED();
    }

    if( xReturn == pdPASS )
    {/*上面的操作成功,接下来判断调度器是否运行,如果调度器运行,
    则判断是否需要进行一次内核抢占,完成任务的切换。切换的条件是当前运行的任务优先级没有新建任务高*/
        if( xSchedulerRunning != pdFALSE )
        {
            /* If the created task is of a higher priority than the current task
            then it should run now. */
            if( pxCurrentTCB->uxPriority < uxPriority )
            {
                /* taskYIELD_IF_USING_PREEMPTION()是configUSE_PREEMPTION控制开关的,
                最后调用portYIELD,进行一次任务软切换*/
                taskYIELD_IF_USING_PREEMPTION();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

    return xReturn;
}

可见,任务创建时freertos为任务分配了空间,完成了一些初始化操作。任务可以在任何地方被创建,调度器以及开始时也可创建,并且会按照优先级进行抢占运行。Freertos又是怎么初始化栈空间内,怎么保证创建的任务函数被顺利执行?

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    /* Setup the initial stack of the task.  The stack is set exactly as
    expected by the portRESTORE_CONTEXT() macro.

    The fist real value on the stack is the status register, which is set for
    system mode, with interrupts enabled.  A few NULLs are added first to ensure
    GDB does not try decoding a non-existent return address. */
    *pxTopOfStack = ( StackType_t ) NULL;
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) NULL;
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) NULL;
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) portINITIAL_SPSR;

    if( ( ( uint32_t ) pxCode & portTHUMB_MODE_ADDRESS ) != 0x00UL )
    {
        /* The task will start in THUMB mode. */
        *pxTopOfStack |= portTHUMB_MODE_BIT;
    }
    /*按照portRESTORE_CONTEXT()操作的逆顺序初始化栈,栈的第一个地址,初始化位portINITIAL_SPSR,
    表示系统运行模式等信息。栈前面几个NULL是为了防止gdb区解释无效的返回地址*/
    pxTopOfStack--;
    /*设置任务执行函数到栈*/
    /* Next the return address, which in this case is the start of the task. */
    *pxTopOfStack = ( StackType_t ) pxCode;
    pxTopOfStack--;
    /*任务返回地址,因为任务一般不需要返回,通常设置为NULL*/
    /* Next all the registers other than the stack pointer. */
    *pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;    /* R14 */
    pxTopOfStack--;
    /*初始化通用寄存器,将任务参数赋值到R0 , pxPortInitialiseStack整个初始化顺序和portSAVE_CONTEXT()的顺序要保持一致,
    这样才能保证portRESTORE_CONTEXT()顺序完成。这两个一般是汇编实现在port_asm_vectors.S*/
*pxTopOfStack = ( StackType_t ) 0x12121212; /* R12 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x11111111; /* R11 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x10101010; /* R10 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x09090909; /* R9 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x08080808; /* R8 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x07070707; /* R7 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x06060606; /* R6 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x05050505; /* R5 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x04040404; /* R4 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x03030303; /* R3 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x02020202; /* R2 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) 0x01010101; /* R1 */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
    pxTopOfStack--;

    /* The task will start with a critical nesting count of 0 as interrupts are
    enabled. */
    /*接着保存了系统临界区进入计数,默认为0*/
    *pxTopOfStack = portNO_CRITICAL_NESTING;
    pxTopOfStack--;

    /* The task will start without a floating point context.  A task that uses
    the floating point hardware must call vPortTaskUsesFPU() before executing
    any floating point instructions. */
/*默认也不为任务设置浮点运算上下文*/
    *pxTopOfStack = portNO_FLOATING_POINT_CONTEXT;

    return pxTopOfStack;
}

pxPortInitialiseStack整个初始化顺序和portSAVE_CONTEXT()的顺序要保持一致,这样才能保证portRESTORE_CONTEXT()顺序完成。这两个一般是汇编实现在port_asm_vectors.S,这个会在合适的章节再进行详细说明。

任务还有一个创建函数xTaskCreateRestricted,按照注释实在MPU中使用,所以也就不再详细看了。

任务创建部分大概介绍完毕,接着该task.h中其他的任务操作函数vTaskDelay、vTaskDelayUntil、vTaskSuspend等等。但是我还是觉得接下来介绍调度器开始函数void vTaskStartScheduler( void )。

调度器开始
同样带着问题去走读源码。任务创建后,vTaskStartScheduler是怎样推了一把调度器的?除了我们创建的任务还有那些任务存在?vTaskStartScheduler在系统级别做了那些初始化工作?调度器开动后是怎么保证生生不息的呢?

void vTaskStartScheduler( void )
{
BaseType_t xReturn;

    /* Add the idle task at the lowest priority. */
    #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
    {
/*freertos默认为我们创建一个IDLE任务,运行函数为prvIdleTask ,优先级默认最低,如果INCLUDE_xTaskGetIdleTaskHandle 为1,
则表示需要获取IDLEtask的任务句柄,负责穿件IDLE task的时候不返还该任务句柄。
prvIdleTask 执行函数会在下面列出*/
        /* Create the idle task, storing its handle in xIdleTaskHandle so it can
        be returned by the xTaskGetIdleTaskHandle() function. */
        xReturn = xTaskCreate( prvIdleTask, "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
    }
    #else
    {
        /* Create the idle task without storing its handle. */
        xReturn = xTaskCreate( prvIdleTask, "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), NULL );  /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
    }
    #endif /* INCLUDE_xTaskGetIdleTaskHandle */

    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
/*如果使用freertos的系统定时器,则还要创建一个定时器任务,创建时与IDLEtask类似,
也可以选择任务堆栈大小和是否返回句柄,但是timer 任务的优先级一般都很高(设置为最大优先级),
这个会在将timer 的时候详细说明原因*/
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_TIMERS */

    if( xReturn == pdPASS )
    {
        /*上面的任务创建成功了,接下来完成初始化*/
        /* Interrupts are turned off here, to ensure a tick does not occur
        before or during the call to xPortStartScheduler().  The stacks of
        the created tasks contain a status word with interrupts switched on
        so interrupts will automatically get re-enabled when the first task
        starts to run. */
        portDISABLE_INTERRUPTS();
        /*如果使用了newlib的C运行库,需要重置_impure_ptr ,详细情况请了解newlib,这里不再详细介绍*/
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            /* Switch Newlib's _impure_ptr variable to point to the _reent
            structure specific to the task that will run first. */
            _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
        }
        #endif /* configUSE_NEWLIB_REENTRANT */
        /*初始化xNextTaskUnblockTime 为最大,xSchedulerRunning 为true,
        xTickCount 为0,马上要开始使能系统tick timer了*/
        xNextTaskUnblockTime = portMAX_DELAY;
        xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) 0U;

        /* If configGENERATE_RUN_TIME_STATS is defined then the following
        macro must be defined to configure the timer/counter used to generate
        the run time counter time base. */
        /*这个宏是用来配置运行信息的计时器的,比如我们要获取系统启动后每一个任务运行的时间信息,
        CPU占用率,那么就必须有一个计数器来计算时间,这个计数器的精度越小,运行信息获取的越准,
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()则是配置定时器,并且为定时器的系统计数设一个初始值*/
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        /* Setting up the timer tick is hardware specific and thus in the
        portable interface. */
        /*不同的平台集成的函数,为系统设置一个的tick timer,每次定时器中断表示一个tick,
        会进行一次调度,tick timer的超时时间和freertos配置的系统HZ计算得出*/
        if( xPortStartScheduler() != pdFALSE )
        {
            /* Should not reach here as if the scheduler is running the
            function will not return. */
        }
        else
        {
            /* Should only reach here if a task calls xTaskEndScheduler(). */
        }
    }
    else
    {
        /* This line will only be reached if the kernel could not be started,
        because there was not enough FreeRTOS heap to create the idle task
        or the timer task. */
        configASSERT( xReturn );
    }
}

vTaskStartScheduler调用xPortStartScheduler实现了硬件定时器驱动系统tick进行调度。Freertos默认创建的任务包括IDLE任务和timer 任务。IDLE任务将在下面介绍,timer任务将留在timer章节单独介绍。我们还会以ARM A9为例解释xPortStartScheduler的具体操作。

BaseType_t xPortStartScheduler( void )
{
uint32_t ulAPSR;

    #if( configASSERT_DEFINED == 1 )
    {
    /*配置了freertos下的断言,具体断言的实现(vApplicationAssert)需要用户自己定义,
    这部分代码检测下集成的中断优先级寄存器是否有效*/
        volatile uint32_t ulOriginalPriority;
        volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( configINTERRUPT_CONTROLLER_BASE_ADDRESS + portINTERRUPT_PRIORITY_REGISTER_OFFSET );
        volatile uint8_t ucMaxPriorityValue;

        /* Determine how many priority bits are implemented in the GIC.

        Save the interrupt priority value that is about to be clobbered. */
        ulOriginalPriority = *pucFirstUserPriorityRegister;

        /* Determine the number of priority bits available.  First write to
        all possible bits. */
        *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

        /* Read the value back to see how many bits stuck. */
        ucMaxPriorityValue = *pucFirstUserPriorityRegister;

        /* Shift to the least significant bits. */
        while( ( ucMaxPriorityValue & portBIT_0_SET ) != portBIT_0_SET )
        {
            ucMaxPriorityValue >>= ( uint8_t ) 0x01;
        }

        /* Sanity check configUNIQUE_INTERRUPT_PRIORITIES matches the read
        value. */
        configASSERT( ucMaxPriorityValue == portLOWEST_INTERRUPT_PRIORITY );

        /* Restore the clobbered interrupt priority register to its original
        value. */
        *pucFirstUserPriorityRegister = ulOriginalPriority;
    }
    #endif /* conifgASSERT_DEFINED */


    /* Only continue if the CPU is not in User mode.  The CPU must be in a
    Privileged mode for the scheduler to start. */
/*读出APSR,判断当前处理器模式,如果是用户模式(权限太低),直接断言*/
    __asm volatile ( "MRS %0, APSR" : "=r" ( ulAPSR ) );
    ulAPSR &= portAPSR_MODE_BITS_MASK;
    configASSERT( ulAPSR != portAPSR_USER_MODE );

    if( ulAPSR != portAPSR_USER_MODE )
    {
        /* Only continue if the binary point value is set to its lowest possible
        setting.  See the comments in vPortValidateInterruptPriority() below for
        more information. */
        configASSERT( ( portICCBPR_BINARY_POINT_REGISTER & portBINARY_POINT_BITS ) <= portMAX_BINARY_POINT_VALUE );

        if( ( portICCBPR_BINARY_POINT_REGISTER & portBINARY_POINT_BITS ) <= portMAX_BINARY_POINT_VALUE )
        {
            /* Interrupts are turned off in the CPU itself to ensure tick does
            not execute while the scheduler is being started.  Interrupts are
            automatically turned back on in the CPU when the first task starts
            executing. */
            /*禁止CPU中断*/
            portCPU_IRQ_DISABLE();
            /*设置系统tick timer,*/
            /* Start the timer that generates the tick ISR. */
            configSETUP_TICK_INTERRUPT();
            /*开始执行系统第一个任务的栈,就是调用任务创建函数后设置的pxCurrentTCB任务*/
            /* Start the first task executing. */
            vPortRestoreTaskContext();
        }
    }

    /* Will only get here if xTaskStartScheduler() was called with the CPU in
    a non-privileged mode or the binary point register was not set to its lowest
    possible value.  prvTaskExitError() is referenced to prevent a compiler
    warning about it being defined but not referenced in the case that the user
    defines their own exit address. */
    /*程序能进入到这里表示错误,可以调用prvTaskExitError 进行错误处理,正常情况下函数不会返回,
除非调度器被终止,否则系统一直调度其他任务,而不会执行到此处*/
    ( void ) prvTaskExitError;
    return 0;
}

configSETUP_TICK_INTERRUPT() 定义为void FreeRTOS_SetupTickInterrupt( void )函数。该函数设置了系统tick timer,一般需要不同的平台自己进行集成。 一般为初始化中断控制器,初始化定时器,定时器超时时间为1/ configTICK_RATE_HZ。并且设置定时器的中断回调为void FreeRTOS_Tick_Handler( void ),这个回调保证每个tick对系统任务进行一次重新调度。这个函数会在下面再详细介绍。
系统tick timer开始后,系统开始执行第一个任务,调用函数vPortRestoreTaskContext,这个函数在portasm.c中。定义如下:

.type vPortRestoreTaskContext, %function
vPortRestoreTaskContext:
    /* Switch to system mode. */
    CPS     #SYS_MODE
    portRESTORE_CONTEXT

就是将处理器模式改为系统模式,然后调用portRESTORE_CONTEXT,所以上面提到的每个栈的初始化时,压栈的顺序和portSAVE_CONTEXT一直,这样才能保证portRESTORE_CONTEXT正确完成。

IDLE任务的函数体如下:

static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
    /* Stop warnings. */
    ( void ) pvParameters;

    for( ;; )
    {
        /* See if any tasks have been deleted. */
/*首先查看xTasksWaitingTermination是否存在等待释放资源的被删除的任务。
任务被删除时,会从其他list移到xTasksWaitingTermination链表,并且在IDLE task中将任务的stack和TCB内存释放掉*/
        prvCheckTasksWaitingTermination();

        #if ( configUSE_PREEMPTION == 0 )
        {
            /* If we are not using preemption we keep forcing a task switch to
            see if any other task has become available.  If we are using
            preemption we don't need to do this as any task becoming available
            will automatically get the processor anyway. */
            /*如果没有配置freertos的抢占内核功能,那么不管是否有优先级高的任务就绪,
            都尝试进行一次软切换,如果有优先级搞得任务,那么IDLE让出CPU,
            如果没有CPU继续调度IDLE任务,IDLE任务执行下一条函数*/
            taskYIELD();
        }
        #endif /* configUSE_PREEMPTION */

        #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
        {
            /* When using preemption tasks of equal priority will be
            timesliced.  If a task that is sharing the idle priority is ready
            to run then the idle task should yield before the end of the
            timeslice.

            A critical region is not required here as we are just reading from
            the list, and an occasional incorrect value will not matter.  If
            the ready list at the idle priority contains more than one task
            then a task other than the idle task is ready to execute. */
        /*如果配置了抢占式内核,和configIDLE_SHOULD_YIELD ,
        那么IDLE任务会检查和IDLE任务同优先级的任务是否有就绪状态,如果有则让出CPU*/    if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
            {
                taskYIELD();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */

        #if ( configUSE_IDLE_HOOK == 1 )
        {
        /*如果配置了configUSE_IDLE_HOOK,可以在IDLE任务中调用一个空闲函数回调, 
        这个函数默认是个weak实现,用户可以再自定义其实现 */
            extern void vApplicationIdleHook( void );

            /* Call the user defined function from within the idle task.  This
            allows the application designer to add background functionality
            without the overhead of a separate task.
            NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
            CALL A FUNCTION THAT MIGHT BLOCK. */
            vApplicationIdleHook();
        }
        #endif /* configUSE_IDLE_HOOK */

        /* This conditional compilation should use inequality to 0, not equality
        to 1.  This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
        user defined low power mode implementations require
        configUSE_TICKLESS_IDLE to be set to a value other than 1. */
        #if ( configUSE_TICKLESS_IDLE != 0 )
        {/* configUSE_TICKLESS_IDLE如果配置,表示用户开启了tickless模式, 
        以达到降功耗的作用。Tickless机制就是,每次空闲任务检查下一个delay的任务距离现在的tick数, 
        如果超过我们期待的值(configEXPECTED_IDLE_TIME_BEFORE_SLEEP),则调用portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime )将cpu进入到睡眠模式。 
        在tick less章节会详细介绍*/
        TickType_t xExpectedIdleTime;

            /* It is not desirable to suspend then resume the scheduler on
            each iteration of the idle task.  Therefore, a preliminary
            test of the expected idle time is performed without the
            scheduler suspended.  The result here is not necessarily
            valid. */
            xExpectedIdleTime = prvGetExpectedIdleTime();

            if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
            {
                vTaskSuspendAll();
                {
                    /* Now the scheduler is suspended, the expected idle
                    time can be sampled again, and this time its value can
                    be used. */
                    configASSERT( xNextTaskUnblockTime >= xTickCount );
                    xExpectedIdleTime = prvGetExpectedIdleTime();

                    if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
                    {
                        traceLOW_POWER_IDLE_BEGIN();
                        portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
                        traceLOW_POWER_IDLE_END();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                ( void ) xTaskResumeAll();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_TICKLESS_IDLE */
    }
}

调度器结束函数:

void vTaskEndScheduler( void )
{
    /* Stop the scheduler interrupts and call the portable scheduler end
    routine so the original ISRs can be restored if necessary.  The port
    layer must ensure interrupts enable bit is left in the correct state. */
    portDISABLE_INTERRUPTS();
    xSchedulerRunning = pdFALSE;
    vPortEndScheduler();
}

就是关掉系统中断,将xSchedulerRunning设置为FALSE。

任务的删除
有任务的创建自然有任务的删除。删除处理如下:

void vTaskDelete( TaskHandle_t xTaskToDelete )
    {
    TCB_t *pxTCB;

        taskENTER_CRITICAL();
        {/*进入临界区*/
            /* If null is passed in here then it is the calling task that is
            being deleted. */
            /*获取要删除的任务上下文,如果入参xTaskToDelete 不为空, 
            则将其进行转换为TCB_t ,如果入参xTaskToDelete 为空,则删除当前正在运行的任务*/
            pxTCB = prvGetTCBFromHandle( xTaskToDelete );

            /* Remove task from the ready list and place in the termination list.
            This will stop the task from be scheduled.  The idle task will check
            the termination list and free up any memory allocated by the
            scheduler for the TCB and stack. */
            /*将任务从调度队列删除,该任务不会再被调度器调度,如果该任务在就绪队列中, 
            那么需要检查该任务优先级的就绪队列是否还有别的任务(调用taskRESET_READY_PRIORITY), 
            如果该任务同等优先级的就绪队列为空,则需要重新设置uxTopReadyPriority, 
            清除uxTopReadyPriority对当前任务优先级的标记*/
            if( uxListRemove( &( pxTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
            {
                taskRESET_READY_PRIORITY( pxTCB->uxPriority );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            /* Is the task waiting on an event also? */
            /*任务是否在等待某个事件,如果是的话,将其从等待事件队列中删除*/
            if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
            {
                ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
            /*将任务添加到xTasksWaitingTermination 队列, 
            任务的内存资源将在IDLE task中完成释放,前面IDLE 函数介绍是有说明*/
            vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xGenericListItem ) );

            /* Increment the ucTasksDeleted variable so the idle task knows
            there is a task that has been deleted and that it should therefore
            check the xTasksWaitingTermination list. */
            ++uxTasksDeleted;

            /* Increment the uxTaskNumberVariable also so kernel aware debuggers
            can detect that the task lists need re-generating. */
            uxTaskNumber++;

            traceTASK_DELETE( pxTCB );
        }
        taskEXIT_CRITICAL();
        /*至此任务删除操作基本完成,退出临界区。这是调度器如果没有停止的话, 
        需要考虑两种情况:1.删除的任务是当前任务,则进行一次上下文切换,调度其他任务; 
        2.删除的是其他任务,但是该任务可能在delay list中,并且有可能是delay list中第一个元素, 
        即下一个即可进入就绪状态的任务,因此prvResetNextTaskUnblockTime 更新xNextTaskUnblockTime(该变量表示下一个进入ready 状态的任务的tick点) */
        /* Force a reschedule if it is the currently running task that has just
        been deleted. */
        if( xSchedulerRunning != pdFALSE )
        {
            if( pxTCB == pxCurrentTCB )
            {
                configASSERT( uxSchedulerSuspended == 0 );

                /* The pre-delete hook is primarily for the Windows simulator,
                in which Windows specific clean up operations are performed,
                after which it is not possible to yield away from this task -
                hence xYieldPending is used to latch that a context switch is
                required. */
                portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
                portYIELD_WITHIN_API();
            }
            else
            {
                /* Reset the next expected unblock time in case it referred to
                the task that has just been deleted. */
                taskENTER_CRITICAL();
                {
                    prvResetNextTaskUnblockTime();
                }
                taskEXIT_CRITICAL();
            }
        }
    }

任务删除函数走读完了,大概可以总结出下面几个结论:当前任务可以删除自己,如果删除任务传入的任务句柄为NULL则表示删除当前任务,任务删除时有可能进行任务切换,任务资源的释放实在IDLE 任务中完成。

当前任务延时
任务延时的单位是ticks。开始介绍前,考虑下如果要我们自己实现任务延时函数,大概要完成那几步? 首先将延时任务,从其他队列删除,然后添加到delay list中,判断延时的tick数是否在当前tick数上发生翻转,以决定将任务加入到正确的delay list中,更新xNextTaskUnblockTime,被延迟的任务一定是当前运行任务,所以要进行一次任务切换。

void vTaskDelay( const TickType_t xTicksToDelay )
{
    TickType_t xTimeToWake;
    BaseType_t xAlreadyYielded = pdFALSE;


        /* A delay time of zero just forces a reschedule. */
        if( xTicksToDelay > ( TickType_t ) 0U )
        {
            configASSERT( uxSchedulerSuspended == 0 );
            vTaskSuspendAll();
            {/*暂停调度器,因为调用vTaskDelay 的任务一定是当前正在运行的任务, 
            为了防止调度器将任务切走,而破坏了任务延时操作的完整性,所以讲调度器暂停*/
                traceTASK_DELAY();

                /* A task that is removed from the event list while the
                scheduler is suspended will not get placed in the ready
                list or removed from the blocked list until the scheduler
                is resumed.

                This task cannot be in an event list as it is the currently
                executing task. */

                /* Calculate the time to wake - this may overflow but this is
                not a problem. */
                /*计算被阻塞的任务的唤醒时间,即当前tick数加上延时tick数*/
                xTimeToWake = xTickCount + xTicksToDelay;

                /* We must remove ourselves from the ready list before adding
                ourselves to the blocked list as the same list item is used for
                both lists. */
                /*将任务从ready list中删除*/
                if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
                {
                    /* The current task must be in a ready list, so there is
                    no need to check, and the port reset macro can be called
                    directly. */
                /*重置最高就绪任务优先级*/             
                    portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                /*将当前任务添加到delay list中,这个函数会判断需要插入的队列 
                (是否唤醒tick点和现在tick点发生了翻转,然后插入相应的队列(请记起前面介绍链表时,插入是排序的插入,即delaylist总是按照唤醒时间来排序的, 因此xNextTaskUnblockTime总是取队列第一个元素。))*/
                prvAddCurrentTaskToDelayedList( xTimeToWake );
            }
            /*调用xTaskResumeAll 恢复调度器,这个函数在下面会详细介绍*/
            xAlreadyYielded = xTaskResumeAll();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* Force a reschedule if xTaskResumeAll has not already done so, we may
        have put ourselves to sleep. */
        /*如果xTaskResumeAll 没有完成调度,则任务自己完成一次任务切换*/
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

调度器暂停和恢复

调度器的暂停和恢复也是两个比较重要的函数。

void vTaskSuspendAll( void )
{
    /* A critical section is not required as the variable is of type
    BaseType_t.  Please read Richard Barry's reply in the following link to a
    post in the FreeRTOS support forum before reporting this as a bug! -
    http://goo.gl/wu4acr */
    /* uxSchedulerSuspended ++;表示调度器停止支持嵌套, 
    如果uxSchedulerSuspended 不为0,这时时严禁调度器切换任务的。 
    这里的注释很有意思,大概是太多人觉得对这个全局变量需要进入临界区保护操作,但是其实不需要,注释还特地说明了下:不要再去论坛里报这个“bug”了。 
    注释里的链接我没有打开过,但是我仔细想了下,确实不需要进入临界区,这个问题可以自己思考下*/
    ++uxSchedulerSuspended;
}

调度器恢复函数:

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

    /* If uxSchedulerSuspended is zero then this function does not match a
    previous call to vTaskSuspendAll(). */
    /*在调度器运行时调用该函数会进入到系统断言*/
    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. */
    taskENTER_CRITICAL();
    {/*进入临界区,uxSchedulerSuspended--,如果uxSchedulerSuspended 自减后为0,说明调度器可以恢复运行了*/
        --uxSchedulerSuspended;

        if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
        {
            if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
            {/*调度器再次开启时,首先要解决的就是xPendingReadyList 中的任务, 
            这些任务在调度器停止期间,状态从其他状态转换为就绪状态,但是调度器此时停止,只好先添加到xPendingReadyList*/
                /* Move any readied tasks from the pending list into the
                appropriate ready list. */
                while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
                {/*取出任务,因为这些任务已经处于就绪状态,因此从其他任务队列中删除,并且删除其时间等待队列, 
                然后添加到ready list中,prvAddTaskToReadyList 将任务添加到任务优先级的就绪队列,同事更新uxTopReadyPriority */
                    pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
                    ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
                    ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
                    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 )
                    {/*如果新加入就绪队列的任务优先级比当前运行任务高,则表示需要一次任务切换, 
                    但是不是马上切换,因为要把xPendingReadyList 中的任务全部取出*/
                        xYieldPending = pdTRUE;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }

                /* 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. */
                if( uxPendedTicks > ( UBaseType_t ) 0U )
                {/*在调度器停止时,如果tick timer中断,触发了一次tick,这是使用uxPendedTicks 记录悬而未决的tick数*/
                    while( uxPendedTicks > ( UBaseType_t ) 0U )
                    {
                        /*调用xTaskIncrementTick 增加tick数,如果xTaskIncrementTick 返回TRUE,表示需要进行一次切换*/
                        if( xTaskIncrementTick() != pdFALSE )
                        {
                            xYieldPending = pdTRUE;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                        --uxPendedTicks;
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                if( xYieldPending == pdTRUE )
                {/*经过上面的操作后,任务需要进行切换,taskYIELD_IF_USING_PREEMPTION()就是进行切换的函数, 
                这个函数在configUSE_PREEMPTION 使能时才有效,如果切换了将xAlreadyYielded 设置为TRUE*/
                    #if( configUSE_PREEMPTION != 0 )
                    {
                        xAlreadyYielded = pdTRUE;
                    }
                    #endif
                    taskYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    taskEXIT_CRITICAL();
    /*退出临界区,返回xAlreadyYielded */
    return xAlreadyYielded;
}

xTaskResumeAll 函数主要处理了xPendingReadyList和uxPendedTicks。因为调度器已经停止,正常情况下任务的状态不会再发生改变,但是xTaskSuspendAll并没有禁止中断,因此考虑两种情况:某个中断回调中操作了queue或者event等消息机制,导致阻塞在该消息上的某个任务从阻塞状态转换为就绪状态,这种情况下任务添加到xPendingReadyList;另外一种情况是,发生的tick timer的中断,这时会使用uxPendedTicks记下未处理的tick数。

系统tick定时器 回调

还记得前面介绍tick timer的中断回调FreeRTOS_Tick_Handler吗。仔细思考下,xTaskResumeAll对uxPendedTicks的处理应该大同小异。

void FreeRTOS_Tick_Handler( void )
{
    /* Set interrupt mask before altering scheduler structures.   The tick
    handler runs at the lowest priority, so interrupts cannot already be masked,
    so there is no need to save and restore the current mask value.  It is
    necessary to turn off interrupts in the CPU itself while the ICCPMR is being
    updated. */
/*禁止CPU中断,获取ICCPMR,使能中断,然后调用xTaskIncrementTick 如果xTaskIncrementTick 返回true,表示任务需要完成一次切换*/
    portCPU_IRQ_DISABLE();
    portICCPMR_PRIORITY_MASK_REGISTER = ( uint32_t ) ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT );
    __asm volatile (    "dsb        \n"
                        "isb        \n" );
    portCPU_IRQ_ENABLE();

    /* Increment the RTOS tick. */
    if( xTaskIncrementTick() != pdFALSE )
    {
        ulPortYieldRequired = pdTRUE;
    }

    /* Ensure all interrupt priorities are active again. */
    portCLEAR_INTERRUPT_MASK();
    configCLEAR_TICK_INTERRUPT();
}

可见xTaskResumeAll和FreeRTOS_Tick_Handler都调用了xTaskIncrementTick。

BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;

    /* Called by the portable layer each time a tick interrupt occurs.
    Increments the tick then checks to see if the new tick value will cause any
    tasks to be unblocked. */
    traceTASK_INCREMENT_TICK( xTickCount );
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {/*如果调度器没有暂停,增加xTickCount */
        /* Increment the RTOS tick, switching the delayed and overflowed
        delayed lists if it wraps to 0. */
        ++xTickCount;

        {
            /* Minor optimisation.  The tick count cannot change in this
            block. */
            /*使用一个常量xConstTickCount保存当前的tick数,防止tick数在下面的操作中被改掉*/
            const TickType_t xConstTickCount = xTickCount;

            if( xConstTickCount == ( TickType_t ) 0U )
            {/*如果xConstTickCount 为0,表示无符号型的tick count发生了翻转, 
            这时调用taskSWITCH_DELAYED_LISTS 进行一次delay list切换, 
            即pxDelayedTaskList 和pxOverflowDelayedTaskList 对调一下,还有将xNumOfOverflows 加一*/
                taskSWITCH_DELAYED_LISTS();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            /* See if this tick has made a timeout expire.  Tasks are stored in
            the queue in the order of their wake time - meaning once one task
            has been found whose block time has not expired there is no need to
            look any further down the list. */
            if( xConstTickCount >= xNextTaskUnblockTime )
            {
            /*如果xConstTickCount 大于或等于xNextTaskUnblockTime, 
            表示有一个延时的任务可以转换为就绪状态了,那么就从pxDelayedTaskList中取出任务,然后更新相关链表 */
                for( ;; )
                {/*再开始这个循环时,请清醒pxDelayedTaskList 是按照唤醒时间排序的链表*/
                    if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                    {/*如果pxDelayedTaskList 元素为空,
                    则将xNextTaskUnblockTime 设置为最大,然后退出循环*/
                        /* The delayed list is empty.  Set xNextTaskUnblockTime
                        to the maximum possible value so it is extremely
                        unlikely that the
                        if( xTickCount >= xNextTaskUnblockTime ) test will pass
                        next time through. */

                        xNextTaskUnblockTime = portMAX_DELAY;
                        break;
                    }
                    else
                    {/*如果xNextTaskUnblockTime 不为空, 
                    则取出xNextTaskUnblockTime 的第一个元素(时间最近的被唤醒的任务)*/
                        /* The delayed list is not empty, get the value of the
                        item at the head of the delayed list.  This is the time
                        at which the task at the head of the delayed list must
                        be removed from the Blocked state. */
                        pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
                        /*取出这个任务后,还要从xGenericListItem 中取出链表元素的itemvalue, 
                        当xGenericListItem 添加到delay list中时,xItemValue 保存了任务的唤醒tick点*/
                        xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xGenericListItem ) );

                        if( xConstTickCount < xItemValue )
                        {/*如果当前的tick数,小于取出的任务的唤醒tick点,表示任务还没有到就绪的时间, 
                        那么设置xNextTaskUnblockTime 为该任务的唤醒tick点, 
                        然后就可以退出pxDelayedTaskList 的操作了*/
                            /* It is not time to unblock this item yet, but the
                            item value is the time at which the task at the head
                            of the blocked list must be removed from the Blocked
                            state - so record the item value in
                            xNextTaskUnblockTime. */
                            xNextTaskUnblockTime = xItemValue;
                            break;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }

                        /* It is time to remove the item from the Blocked state. */
            /*程序能走到这里,表示重delay list中取出的任务满足唤醒的条件, 
            因此将其从delay list 和event list中删除*/
                        ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );

                        /* Is the task waiting on an event also?  If so remove
                        it from the event list. */
                        if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                        {
                            ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }

                        /* Place the unblocked task into the appropriate ready
                        list. */
            /*将任务添加到就绪队列*/
                        prvAddTaskToReadyList( pxTCB );

                        /* A task being unblocked cannot cause an immediate
                        context switch if preemption is turned off. */
                        #if (  configUSE_PREEMPTION == 1 )
                        {
                            /* Preemption is on, but a context switch should
                            only be performed if the unblocked task has a
                            priority that is equal to or higher than the
                            currently executing task. */
                            if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                            {/*如果新就绪的任务优先级比当前运行任务的优先级高,设置xSwitchRequired 为ture*/
                                xSwitchRequired = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        #endif /* configUSE_PREEMPTION */
                    }
                }
            }
        }

        /* Tasks of equal priority to the currently running task will share
        processing time (time slice) if preemption is on, and the application
        writer has not explicitly turned time slicing off. */
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
        {
        /*如果使能了抢占式内核,同时使能了configUSE_TIME_SLICING , 
        每次tick时,会判断当前任务同等优先级的就绪队列中,是否还有等待执行的任务, 
        如果有就让出CPU给其他通优先级的任务。这种调度策略叫做时间片共享*/ if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
            {
                xSwitchRequired = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

        #if ( configUSE_TICK_HOOK == 1 )
        {
            /* Guard against the tick hook being called when the pended tick
            count is being unwound (when the scheduler is being unlocked). */
            /* configUSE_TICK_HOOK使能后将在每次tick回调中调用vApplicationTickHook , 
            这个回调时weak声明,用户可以自己定义实现*/
            if( uxPendedTicks == ( UBaseType_t ) 0U )
            {
                vApplicationTickHook();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_TICK_HOOK */
    }
    else
    {/*如果调度器这时候是暂停状态,uxPendedTicks 自加一*/
        ++uxPendedTicks;

        /* The tick hook gets called at regular intervals, even if the
        scheduler is locked. */
        #if ( configUSE_TICK_HOOK == 1 )
        {
            vApplicationTickHook();
        }
        #endif
    }

    #if ( configUSE_PREEMPTION == 1 )
    {/*如果调度器暂停时,xYieldPending 为ture,那么同样需要一次任务切换,
    xYieldPending 表示需要至少有一次任务切换,没有被处理*/
        if( xYieldPending != pdFALSE )
        {
            xSwitchRequired = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_PREEMPTION */
    /*返回是否需要任务切换的值*/
    return xSwitchRequired;
}

任务上下文的切换

前面一直在讲任务的切换,那么任务又是如何完成切换的。这需要去portASM.S中寻找答案。
每次调用任务切换相关的API时,总是提到portYIELD 。

#define portYIELD() __asm volatile ( "SWI 0" );

执行了SWI命令,产生了一个0号的软中断。在中断向量组中(port_asm_vectors.S),软中断执行FreeRTOS_SWI_Handler:

_swi:   .word FreeRTOS_SWI_Handler

FreeRTOS_SWI_Handler(portASM.S)得具体操作如下:

/******************************************************************************
 * SVC handler is used to start the scheduler.
 *****************************************************************************/
.align 4
.type FreeRTOS_SWI_Handler, %function
FreeRTOS_SWI_Handler:
    /* Save the context of the current task and select a new task to run. */
    portSAVE_CONTEXT
    LDR R0, vTaskSwitchContextConst
    BLX R0
    portRESTORE_CONTEXT

先保存当前任务的上下文,然后调用vTaskSwitchContextConst完成任务切换,这个函数会重置当前运行任务pxCurrentTCB,然后再恢复重置的pxCurrentTCB的上下文,看是运行新任务。
vTaskSwitchContextConst函数其实就是vTaskSwitchContext函数。

vTaskSwitchContextConst: .word vTaskSwitchContext
void vTaskSwitchContext( void )
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
    {
        /* The scheduler is currently suspended - do not allow a context
        switch. */
        /*调度器暂停则设置xYieldPending 为true*/
        xYieldPending = pdTRUE;
    }
    else
    {/*调度器正在运行则清楚xYieldPending 标记*/
        xYieldPending = pdFALSE;
        traceTASK_SWITCHED_OUT();

        #if ( configGENERATE_RUN_TIME_STATS == 1 )
        {/*如果配置了configGENERATE_RUN_TIME_STATS , 
        要在当前任务切出的时候计算当前任务的运行时间*/
                #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
                    portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
                #else
                    ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
                #endif

                /* Add the amount of time the task has been running to the
                accumulated time so far.  The time the task started running was
                stored in ulTaskSwitchedInTime.  Note that there is no overflow
                protection here so count values are only valid until the timer
                overflows.  The guard against negative values is to protect
                against suspect run time stat counter implementations - which
                are provided by the application, not the kernel. */
                if( ulTotalRunTime > ulTaskSwitchedInTime )
                {/*如果此次调度到任务后,任务运行的时间大于切换消耗的时间, 
                则更新任务的总运行时间。同时位一次运行的任务保存任务切入时间*/
                    pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                ulTaskSwitchedInTime = ulTotalRunTime;
        }
        #endif /* configGENERATE_RUN_TIME_STATS */

        /* Check for stack overflow, if configured. */
        /*检查当前任务的栈是否出现了溢出,检查方法就是检测栈的空间的顶部的设置的标记是否被清除了,
        如果栈溢出则调用栈溢出回调vApplicationStackOverflowHook */
        taskCHECK_FOR_STACK_OVERFLOW();

        /* Select a new task to run using either the generic C or port
        optimised asm code. */
/*选择下一个运行的任务,当然是选择优先级最高的任务了*/
        taskSELECT_HIGHEST_PRIORITY_TASK();
        traceTASK_SWITCHED_IN();

        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {/*如果使用了newlib,重置_impure_ptr */
            /* Switch Newlib's _impure_ptr variable to point to the _reent
            structure specific to this task. */
            _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
        }
        #endif /* configUSE_NEWLIB_REENTRANT */
    }
}
taskSELECT_HIGHEST_PRIORITY_TASK()的实现如下:
    #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                      \
    {                                                                                               \
    UBaseType_t uxTopPriority;                                                                      \
                                                                                                    \
        /* Find the highest priority queue that contains ready tasks. */                            \
        /*选择就绪队列里的最高优先级*/   
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                              \
    configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );     \
    /*获取该就绪队列里的下一个任务元素,并将其赋值给pxCurrentTCB 。 
    listGET_OWNER_OF_NEXT_ENTRY 在链表介绍过了,即便是切换的优先级和当前任务优先级一致也能保证pxCurrentTCB 指向下一个任务*/   
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );       \
    } 

任务上下文的保存和恢复,中断回调的处理在portASM.S中都有说明,以后会用专门的篇幅来介绍。
任务的其他操作函数在下一章节继续。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值