9.3 任务调度—任务切换

1 PendSV中断

任务切换的本质:就是CPU寄存器的切换。

假设当由任务A切换到任务B时,主要分两步:

第一步:需暂停任务A的执行,并将此时任务A的寄存器(即CPU寄存器)保存到任务堆栈,这个过程叫做保护现场。

第二步:将任务B的各个寄存器值(被存于堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;

对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换

注意:任务切换是在PendSV中断服务函数里边完成的

PendSV中断的触发主要有两个途径:

1)SysTick嘀嗒定时器中断调用

2)执行FreeRTOS提供的相关API函数,如portYIELED()

但PendSV中断的触发本质是通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSV来启动PendSV中断

2 滴答定时器中断调用

#define portNVIC_INT_CTRL_REG     ( *( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT    ( 1UL << 28UL )

void SysTick_Handler(void)
{
    HAL_IncTick();
	
	if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
	{
		xPortSysTickHandler();
        /*只要任务调度器开启了,就会调用该函数*/
	}
}

void xPortSysTickHandler( void )
{
   vPortRaiseBASEPRI();
    /*屏蔽所有受FreeRTOS管理的中断,因为SysTick的中断优先级设置为最低的中断优先等级
     *因此需要屏蔽所有受FreeRTOS管理的中断*/
    {
        if( xTaskIncrementTick() != pdFALSE )
        /*处理系统时钟节拍,并决定是否进行任务切换*/
        {
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;  
            /*将ICSR寄存器的28位置1,因此触发PendSV中断*/
        }
    }
    vPortClearBASEPRIFromISR();
    /*取消中断屏蔽*/
}

3 FreeRTOS相关API函数

 #define portYIELD()                                
    {                                                   
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 
        /* 设置中断控制状态寄存器,以触发 PendSV 中断 */
                                                        
        __dsb( portSY_FULL_READ_WRITE );                           
        __isb( portSY_FULL_READ_WRITE );                           
    }

4 PendSV中断服务函数

__asm void xPortPendSVHandler( void )
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;
    /*导入全局变量及函数*/

    PRESERVE8
    /*8字节对齐*/

    mrs r0, psp  
    isb
    /*r0为psp,即当前运行任务的任务栈指针*/

    ldr r3, =pxCurrentTCB
    ldr r2, [ r3 ]
	/*r3为pxCurrentTCB的地址值,即指向当前运行任务控制块的指针*/
    /*r2为pxCurrentTCB的值,即当前运行任务控制块的首地址*/

    tst r14, #0x10  
    /*判断R14的第四位是否为1,如果是1,则代表不使用浮点数,如果是0代表使用浮点数,
	 *并且如果使用这些浮点数,那么还需要压栈,保存这些寄存器{s16-s31},s0_s15是自动保存和恢复        
     *的,s16-s31需要手动保存和恢复*/

    it eq
    vstmdbeq r0!, {s16-s31}

    stmdb r0!, {r4-r11, r14}  
    /*压栈指令,将r4~r11入栈到当前运行任务控制块中*/
   
    str r0, [ r2 ]
    /*此时r0位于最底下的栈底,然后将r0的地址写入到r2所指向的内存里面,此时r2是栈顶指针*/

    stmdb sp!, {r0, r3}
    /*将r0~r3入栈到MSP指向的栈中*/

    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    dsb
    isb
    /*屏蔽所有受FreeRTOS管理的所有中断(中断优先级为5~15),即关中断*/

    bl vTaskSwitchContext
    /*跳转到函数vTaskSwitchContext主要用于更新pxCurrentTCB,
     *使其指向最高优先级的就绪态任务*/

    mov r0, #0
    msr basepri, r0
    /*使能所有中断*/

    ldmia sp!, {r0, r3}
    /*将r0、r3重新从MSP指向的栈中出栈*/

    ldr r1, [ r3 ]
    /*注意:r3为pxCurrentTCB的地址值,pxCurrentTCB已经在函数vTaskSwitchContext中更新为最高优        
     *先级的就绪态任务,因此r1为pxCurrentTCB的值,即当前最高优先级就绪态任务控制块的首地址*/

    ldr r0, [ r1 ]
    /*r0为最高优先级就绪态任务的任务指针*/

    ldmia r0!, {r4-r11, r14}   
    /*从最高优先级就绪态任务的任务栈中出栈r4~r14
     *注意:这里出栈的r14为EXC_RETURN,其保存了任务是否使用了浮点单元的信息*/

    tst r14, #0x10  
    it eq
    vldmiaeq r0!, {s16-s31}
    /*此时r14为EXC_RETURN,通过判断EXC_RETURN的bit4是否为0,
     *判断任务是否使用的浮点单元,
     *若使用了浮点单元,则需要从任务栈中恢复出浮点寄存器的值*/

    msr psp, r0  
    isb
    /*更新psp为任务切换后的任务栈指针*/

    #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
        #if WORKAROUND_PMU_CM001 == 1
            push { r14 }
            pop { pc }
            nop
        #endif
    #endif
    /*用于修改XMC4000的BUG,不用理会*/

    bx r14  
    /*跳转到切换后的任务运行,执行此指令,CPU会自动从PSP指向的任务栈中,
     *出栈r0 、r1 、r2 、r3 、r12 、lr 、pc 、xpsr寄存器,接着CPU就跳转到PC指向的代码位置
     *运行,也就是任务上次切换时运行到的位置*/
}

注意:保存和恢复用的都是PSP

/*查找最高优先级的就绪态任务*/
void vTaskSwitchContext( void )    
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
    /*判断任务调度器是否运行*/
    {
        xYieldPending = pdTRUE;
        /*此全局变量用于在系统运行的任意时刻标记需要进行任务切换,会在SysTick的中断服务函数中 
         *统一处理,任务调度器没有运行,不允许任务切换,因此将xYieldPending设置为pdTRUE,那么 
         *系统会在SysTick的中断服务函数中持续发起任务切换,直到任务调度器运行*/
    }
    else
    {
        xYieldPending = pdFALSE;
        /*可以执行任务切换,因此将xYieldPending 设置为pdFalse*/        

        traceTASK_SWITCHED_OUT();
        /*用于调试不用理会*/

        #if ( configGENERATE_RUN_TIME_STATS == 1 )
        /*此宏用于使能任务运行时间,不用理会*/
            {
                #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
                    portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
                #else
                    ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
                #endif

                if( ulTotalRunTime > ulTaskSwitchedInTime )
                {
                    pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime -     
                    ulTaskSwitchedInTime );
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                ulTaskSwitchedInTime = ulTotalRunTime;
            }
        #endif 
       
        taskCHECK_FOR_STACK_OVERFLOW();
        /*检查任务栈是否溢出,未定义,不用理会*/

        #if ( configUSE_POSIX_ERRNO == 1 )
        /*此宏为POXI相关配置,不用理会*/
         {
                pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
         }
        #endif
        
       taskSELECT_HIGHEST_PRIORITY_TASK();
       /*将pxCurrentTCB指向最高优先级的就绪态任务,两种方法:
        *1、滴答定时器中断调用
        *2、执行FreeRTOS提供的相关API函数*/

       traceTASK_SWITCHED_IN();
       /*用于调试,不用理会*/

        #if ( configUSE_POSIX_ERRNO == 1 )
        /*此宏为POXI相关配置,不用理会*/
            {
                FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
            }
        #endif

        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        /* 此宏为 Newlib 相关配置,不用理会 */
            {
               _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
            }
        #endif 
    }
}

函数vTaskSwitchContext()调用了函数taskSELECT_PRIORITY_TASK(),来将pxCurrentTCB设置为指向优先级最高的就绪态任务。

#define taskSELECT_HIGHEST_PRIORITY_TASK()                                                  
{                                                                                           
   UBaseType_t uxTopPriority; 
   /*全局变量uxTopReadyPriority以位图方式记录了系统中存在任务的优先级
    *将遍历的起始优先级设置为全局变量,而无需从系统支持优先级的最大值开始遍历,
    *可以节约一定的遍历时间*/                                                                                                                                                           
                                
    portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                          
    /*获取当前最高优先级的任务*/

    configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); 
    listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );  
    /*根据当前任务的最高优先级获取当前最高优先级的任务控制块 */

    } 

5 前导置零指令

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )     
        uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

在使用硬件方式实现的函数taskSELECT_highest_prioty_task()中调用了函数portGET_HIGHEST_PRIORITY()来计算任务优先级记录中的最高任务优先级,宏portGET_HIGHEST_PRIORITY()使用了_clz这个硬件指定来计算uxReadyPriority的前导零,然后使用31(变量uxReadyPriority的最大比特位)减去得到的前导零,那么就得到了变量uxReadyPriority中,最高位1的比特位。使用此方法就限制了系统最大的优先级数量不能超多 32,即最高优先等级位 31,不过对于绝大多数的应用场合, 32 个任务优先级等级已经足够使用了。

所谓的前导零指令,可以简单的理解为计算一个32位,头部0的个数

通过前导零指令获得最高优先级。

/*通过该函数获取当前最高优先级任务的任务控制块*/
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           
    {                                                                                          
     List_t * const pxConstList = ( pxList );  
     /*确定当前列表*/                                           
                            
     ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 
      /*将当前列表的P指针指向P指针指向的下一个列表项(如图为列表项1)*/
                          
     if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) 
     /*判断当前P指针指向的是否为末尾列表项*/
     {                                                                                      
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
        /*若P指针当前指向的是末尾列表项,则将P指针指向P指针指向下一个列表项/
                       
     }                                                                                      
        ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;   
        /*若P指针指向的不是末尾列表项,则将该列表项确定为当前任务控制块*/                                      
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值