FreeRTOS任务调度研究

来源:互联网 发布:飞利浦 阿里云 编辑:程序博客网 时间:2024/06/06 16:58

这篇文章不介绍FreeRTOS移植,只是最近针对多核ARM Cortex系列平台做了移植后的一篇总结研究文章。所以不涉及对FreeRTOS整体的介绍,而只是分析任务调度这一块的机制。对应的Demo参考自CORTEX_A9_Zynq_ZC702。

一、触发任务调度的两种机制

  1. taskYIELD()
    这种机制其实是通过ARM swi 异常触发task context switch。
  2. IRQ handler
    通过tick中断,来检测是否需要进行任务切换。

最终两者任务切换的方式过程都是一样的,以taskYIELD()为例,代码如下:

/****************************************************************************** * SVC handler is used to start the scheduler. *****************************************************************************/.align 4.type FreeRTOS_SWI_Handler, %functionFreeRTOS_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

ps.这是一个weak函数,可以自行重定义。

二、任务调度过程

总的来说,任务调度包含三个部分:保存当前任务现场;选择下一个执行的任务;恢复任务现场。

FreeRTOS保护的现场都通过system模式进行访问,针对每个任务,在其创建的时候都会在栈内开辟一段固定大小的空间用来保存之前任务的上下文(包括CPU状态,运行栈,内部寄存器)。

为什么是system mode? 因为system mode和usr mode看到的是一份寄存器内容。如下图所示

这里写图片描述

能反应usr模式下的运行状态,又不会影响其他特权模式。

而且system mode会进到PL1 state

这里写图片描述

2.1 恢复现场(通常也是触发执行第一个task的方式)

.macro portRESTORE_CONTEXT    /* Set the SP to point to the stack of the task being restored. */    LDR     R0, pxCurrentTCBConst  // 当前要恢复的任务对应的TCB    LDR     R1, [R0]    LDR     SP, [R1]    // TCB的第一个成员(pxTopOfStack),创建任务的时候根据A系列上下文内容计算得到,恢复的时候通过这个SP来发现保存的上下文,注意:这个时候是system mode。    // 下面就是需要恢复的上下文,和创建任务时保存的顺序相反(push/pop)。    /* Is there a floating point context to restore?  If the restored    ulPortTaskHasFPUContext is zero then no. */    LDR     R0, ulPortTaskHasFPUContextConst    POP     {R1}    STR     R1, [R0]    CMP     R1, #0    /* Restore the floating point context, if any. */    POPNE   {R0}    VPOPNE  {D16-D31}    VPOPNE  {D0-D15}    VMSRNE  FPSCR, R0    /* Restore the critical section nesting depth. */    LDR     R0, ulCriticalNestingConst    POP     {R1}    STR     R1, [R0]    /* Ensure the priority mask is correct for the critical nesting depth. */    LDR     R2, ulICCPMRConst    LDR     R2, [R2]    CMP     R1, #0    MOVEQ   R4, #255    LDRNE   R4, ulMaxAPIPriorityMaskConst    LDRNE   R4, [R4]    STR     R4, [R2]    /* Restore all system mode registers other than the SP (which is already    being used). */    POP     {R0-R12, R14}    /* Return to the task code, loading CPSR on the way. */    RFEIA   sp!          // 执行完这条指定之后system mode下的sp重新指向了真正的栈顶,然后CPU回到任务lr指定的位置(初始值为任务对应的函数入口)。    .endm

在创建完任务,调用vTaskStartScheduler()之后就是通过这个宏进到第一个任务的。

2.2 保存现场

和恢复现场相比较,保存现场就是要把各种状态放到SP(pxTopOfStack)对应的地方上。代码如下:

.macro portSAVE_CONTEXT    /* Save the LR and SPSR onto the system mode stack before switching to    system mode to save the remaining system mode registers. */    SRSDB   sp!, #SYS_MODE   // 先把lr和spsr入栈到system mode的栈中    CPS     #SYS_MODE        // 切到system mode    PUSH    {R0-R12, R14}    // 和恢复现场的顺序对应,寄存器入栈    /* Push the critical nesting count. */    LDR     R2, ulCriticalNestingConst    LDR     R1, [R2]    PUSH    {R1}    /* Does the task have a floating point context that needs saving?  If    ulPortTaskHasFPUContext is 0 then no. */    LDR     R2, ulPortTaskHasFPUContextConst    LDR     R3, [R2]    CMP     R3, #0    /* Save the floating point context, if any. */    FMRXNE  R1,  FPSCR    VPUSHNE {D0-D15}    VPUSHNE {D16-D31}    PUSHNE  {R1}    /* Save ulPortTaskHasFPUContext itself. */    PUSH    {R3}    /* Save the stack pointer in the TCB. */    LDR     R0, pxCurrentTCBConst    LDR     R1, [R0]    STR     SP, [R1]            // 这个时候的sp还是system mode下的sp,所以上下文内容都是在system mode下进行保存和切换的。    .endm

将save context和restore context结合起来看,任务上下文的切换始终发生在system mode,即能改变user mode下的运行栈,又不会破坏其他特权级别(诸如svc,irq,fiq,hyp)下的运行栈。是一个实质上介于特权级别和用户级别的一个中间层级。

2.3 任务切换

任务切换方法由vTaskSwitchContext提供,负责从当前的ready list中选择一个优先级最高的任务。保存到pxCurrentTCB中。

至此,整个FreeRTOS的调度程序就工作起来了。

三、Tick Handler

前面讲到,触发FreeRTOS调度的有两种机制,一是swi handler,二是irq handler,准确的讲是tick handler。

一般针对每一个平台的port,都会实现一个对应的FreeRTOS_Tick_Handler,以Cortex A9为例,其位于Source/portable/GCC/ARM_CA9/port.c中。

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. */    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();}

里面主要就是一个函数:xTaskIncrementTick()。这个函数位于Source/tasks.c。

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 )    {        /* Minor optimisation.  The tick count cannot change in this        block. */        const TickType_t xConstTickCount = xTickCount + 1;        /* Increment the RTOS tick, switching the delayed and overflowed        delayed lists if it wraps to 0. */        xTickCount = xConstTickCount;        if( xConstTickCount == ( TickType_t ) 0U )        {            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 )        {            for( ;; )            {                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )                {                    /* 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; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */                    break;                }                else                {                    /* 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 );                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );                    if( xConstTickCount < xItemValue )                    {                        /* 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. */                    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );                    /* 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 = 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 ) )        {            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). */            if( uxPendedTicks == ( UBaseType_t ) 0U )            {                vApplicationTickHook();            }            else            {                mtCOVERAGE_TEST_MARKER();            }        }        #endif /* configUSE_TICK_HOOK */    }    else    {        ++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 )    {        if( xYieldPending != pdFALSE )        {            xSwitchRequired = pdTRUE;        }        else        {            mtCOVERAGE_TEST_MARKER();        }    }    #endif /* configUSE_PREEMPTION */    return xSwitchRequired;}

总的来讲,xTaskIncrementTick负责更新block list和ready list。然后设置标志位xSwitchRequired,告诉调度器是否需要进行任务切换。

整个函数分为两个分支: uxSchedulerSuspended == ( UBaseType_t ) pdFALSE 和 uxSchedulerSuspended != ( UBaseType_t ) pdFALSE 。

如果此时scheduler已经挂起的话,什么都不做。否则,做如下动作:

  1. 判断当前tick是否大于xNextTaskUnblockTime(初始值为0),如果大于,则需要进一步判断pxDelayedTaskList中的哪些task需要进入到readyList。
  2. 取出pxDelayedTaskList中的最小unblock time(xItemValue)的任务,如果当前tick小于该最小值(表示没有任务满足unblock需求),那么设置xNextTaskUnblockTime为xItemValue,然后退出。
  3. 否则的话,通过( void ) uxListRemove( &( pxTCB->xStateListItem ) );将该task从block状态移除。紧接着判断是不是在等待event,如果是的话,也从event list移除。
  4. 把该任务加到readyList中去。等待调度。判断是否支持preemption,如果支持的话,置xSwitchRequired。
原创粉丝点击