任务切换
任务堆栈与保护恢复现场
RTOS对每个任务均维护有一个堆栈,在进行任务调度时,RTOS会将任务的运行环境(程序指针,寄存器或变量的值等)信息压入堆栈中,当任务再次运行时,调度器会从而堆栈中取出数据,还原当时的运行环境;M3核有PSP:任务堆栈指针,会自动恢复部分寄存器的值
PendSV异常
PendSV(可挂起异常)与SVC不同,它是不精确的,可以子啊更高优先级的异常处理内挂起它,当更高优先级处理完成后PendSV执行。
FreeRTOS在PendSV中断中完成任务切换
任务切换的场合
-
系统调用
#define taskYIELD() portYIELD() #define portYIELD() \ { \ /* Set a PendSV to request a context switch. */ \ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ \ /* Barriers are normally not required but do ensure the code is completely \ within the specified behaviour for the architecture. */ \ __dsb( portSY_FULL_READ_WRITE ); \ __isb( portSY_FULL_READ_WRITE ); \ } #define portNVIC_PENDSVSET_BIT ( 1UL << 28UL ) #define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
将0xe000ed04地址的第28位置1,挂起一个PendSV中断
-
滴答定时器中断触发
滴答定时器中断服务函数:
void SysTick_Handler(void) { //判断任务调度器状态 if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); } } void xPortSysTickHandler( void ) { //关中断 vPortRaiseBASEPRI(); { //更新时钟节拍计数器,并检查是否有任务需要取消阻塞 xTaskIncrementTick if( xTaskIncrementTick() != pdFALSE ) { //挂起一个PendSV中断,通过操作寄存器 portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } } //开中断 vPortClearBASEPRIFromISR(); }
更新时钟节拍计数器,并检查是否有任务需要取消阻塞 xTaskIncrementTick
PendSV中断服务函数
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp //读取任务栈指针
isb
ldr r3, =pxCurrentTCB //获取当前任务TCB地址
ldr r2, [r3]
stmdb r0!, {r4-r11} //将寄存器r4-r11的值压入栈中
str r0, [r2] //将新的栈顶指针保存到r0
stmdb sp!, {r3, r14}
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0 //关中断
dsb
isb //数据和指令同步
bl vTaskSwitchContext //加载函数,获取下一个要运行的任务,并更新pxCurrentTCB
mov r0, #0
msr basepri, r0 //开中断
ldmia sp!, {r3, r14}
ldr r1, [r3]
ldr r0, [r1] //获取新任务的任务栈顶
ldmia r0!, {r4-r11} //恢复寄存器r4-r11的值
msr psp, r0 //将新的栈顶赋值给任务栈指针,其他的寄存器值会自动恢复
isb
bx r14
nop
}
获取下一个要运行的任务
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();
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW();
//获取下一个要运行的任务
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();
}
}
- 获取下一个要运行的任务分为通用方法和硬件方式
通用方法
#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 */
uxTopReadyPriority存储就绪列表的最高优先级,从就绪列表数组中取出一个任务块,将其赋给pxCurrentTCB,移动当前列表指针
硬件方法
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
/* Find the highest priority list that contains ready tasks. */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
portGET_HIGHEST_PRIORITY:获取就绪态的最高优先级
从就绪列表数组中取出一个任务块,将其赋给pxCurrentTCB
优先级数量受位的影响,因为优先级用位图表示
时间片轮转
启用了时间片轮转的宏时,在滴答定时器中断中会对就绪列表中当前优先级任务数量进行判断,如果其他任务则进行任务切换