【简介】
在 cortex-m7 架构下FreeRTOS任务切换是在pendsv 中断函数中进行的,触发任务调度的契机有信号量等待,任务主动释放cpu,以及任务抢占等场景。
在开启任务抢占后会在systick 中断中检查当前是否有更高的优先级的任务可以打断当前任务的运行。
如果xTaskIncrementTick 返回的TRUE 则会触发pendsv异常进行任务切换。
像vTaskDelay 接口会主动释放CPU使用权,接口中会调用portYIELD_WITHIN_API(),
对应portYIELD_WITHIN_API 宏定义如下
上述的场景最终对会触发PENDSV 异常,以下是pendsv异常的处理代码
/*-----------------------------------------------------------*/ xPortPendSVHandler: mrs r0, psp isb /* Get the location of the current TCB. */ ldr r3, =pxCurrentTCB ldr r2, [r3] /* Is the task using the FPU context? If so, push high vfp registers. */ tst r14, #0x10 it eq vstmdbeq r0!, {s16-s31} /* Save the core registers. */ stmdb r0!, {r4-r11, r14} /* Save the new top of stack into the first member of the TCB. */ str r0, [r2] stmdb sp!, {r0, r3} mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY cpsid i msr basepri, r0 dsb isb cpsie i bl vTaskSwitchContext mov r0, #0 msr basepri, r0 ldmia sp!, {r0, r3} /* The first item in pxCurrentTCB is the task top of stack. */ ldr r1, [r3] ldr r0, [r1] /* Pop the core registers. */ ldmia r0!, {r4-r11, r14} /* Is the task using the FPU context? If so, pop the high vfp registers too. */ tst r14, #0x10 it eq vldmiaeq r0!, {s16-s31} msr psp, r0 isb #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */ #if WORKAROUND_PMU_CM001 == 1 push { r14 } pop { pc } #endif #endif bx r14
任务切换的处理主要包含“保存上文” 和 “切换下文” 的处理逻辑
【保存上文代码处理】
在cortex-m 硬件体系结构下,进入异常前硬件会自动的将R0~R3,R12,LR(R14),PC(R15)自动的进行压栈处理
如果进入异常前又在使用浮点相关的寄存器,硬件会将浮点相关的S0~S15,FPSCR寄存器进行压栈处理
上述的硬件压栈处理逻辑方便我们理解保存上下文时有些寄存器未进行保存的原因。
/*-----------------------------------------------------------*/ xPortPendSVHandler: mrs r0, psp isb /* Get the location of the current TCB. */ ldr r3, =pxCurrentTCB ldr r2, [r3] /* Is the task using the FPU context? If so, push high vfp registers. */ tst r14, #0x10 it eq vstmdbeq r0!, {s16-s31} /* Save the core registers. */ stmdb r0!, {r4-r11, r14} /* Save the new top of stack into the first member of the TCB. */ str r0, [r2] stmdb sp!, {r0, r3} mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 dsb isb
上述代码
1. 读取PSP指针至R0 //mrs r0, psp
2. 读取pxCurrentTCB 指针保存至 R3 //ldr r3, =pxCurrentTCB
3.获取pxCurrentTCB 指针的内容至R2 ,TCB 的第一个成员为pxTopOfStack //ldr r2, [r3]
4.切换时如果使用了FP单元,则保存s16~s31 寄存器 //vstmdbeq r0!, {s16-s31}
5.保存r4~r11,r14 寄存器 // stmdb r0!, {r4-r11, r14}
6.更新 pxTopOfStack 成员地址 //str r0, [r2]
7.对r0,r3 寄存器的内容压栈,压入MSP//之后调用了vTaskSwitchContext 函数有可能会改变寄存器的值所以进行了压栈处理
【查找最高优先级任务】
对应处理函数如下
void vTaskSwitchContext( void ) { if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) { /* The scheduler is currently suspended - do not allow a context * switch. */ xYieldPending = pdTRUE; } else { 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 /* 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. */ taskCHECK_FOR_STACK_OVERFLOW(); /* Before the currently running task is switched out, save its errno. */ #if ( configUSE_POSIX_ERRNO == 1 ) { pxCurrentTCB->iTaskErrno = FreeRTOS_errno; } #endif /* Select a new task to run using either the generic C or port * optimised asm code. */ taskSELECT_HIGHEST_PRIORITY_TASK(); /*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. */ traceTASK_SWITCHED_IN(); /* After the new task is switched in, update the global errno. */ #if ( configUSE_POSIX_ERRNO == 1 ) { FreeRTOS_errno = pxCurrentTCB->iTaskErrno; } #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) { /* Switch Newlib's _impure_ptr variable to point to the _reent * structure specific to this task. * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html * for additional information. */ _impure_ptr = &( pxCurrentTCB->xNewLib_reent ); } #endif /* configUSE_NEWLIB_REENTRANT */ } }
上述代码的 taskSELECT_HIGHEST_PRIORITY_TASK 宏定义会从任务链表中选取当前最高优先级的任务并保存至pxCurrentTCB完成任务查找
#define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority = uxTopReadyPriority; \ \ /* Find the highest priority queue that contains ready tasks. */ \ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \ { \ configASSERT( uxTopPriority ); \ --uxTopPriority; \ } \ \ /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \ * the same priority get an equal share of the processor time. */ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ uxTopReadyPriority = uxTopPriority; \ } /* taskSELECT_HIGHEST_PRIORITY_TASK */
【切换下文代码处理】
在获取到最高优先级任务后,会执行如下代码来切换下文
bl vTaskSwitchContext mov r0, #0 msr basepri, r0 ldmia sp!, {r0, r3} /* The first item in pxCurrentTCB is the task top of stack. */ ldr r1, [r3] ldr r0, [r1] /* Pop the core registers. */ ldmia r0!, {r4-r11, r14} /* Is the task using the FPU context? If so, pop the high vfp registers too. */ tst r14, #0x10 it eq vldmiaeq r0!, {s16-s31} msr psp, r0 isb #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */ #if WORKAROUND_PMU_CM001 == 1 push { r14 } pop { pc } #endif #endif bx r14
1 . 获取pxCurrentTCB->pxTopOfStack 成员信息 // ldr r1, [r3] r3=pxCurrentTCB ldr r0, [r1]
2. 从 pxCurrentTCB->pxTopOfStack 中恢复 r4~r11,r14(lr) //ldmia r0!, {r4-r11, r14}
3.从R14中获取当前栈帧是否包含FPU 寄存器 ,如果使用pop FPU 相关软件push 寄存器帧 s16~s31 //tst r14, #0x10 it eq vldmiaeq r0!, {s16-s31}
4,跟新PSP 寄存器为新任务栈帧 // msr psp, r0
5.根据R14 EXEC_RET 的返回标注返回应用程序执行
以上为FreeRTOS 任务上下文的切换过程理解