任务调度的初始化及上下文切换原理
- 前言
- 任务调度整体框架
- 任务调度初始化源码分析
- 启动第一个任务分析
- PendSV中断
- systick定时中断
前言
本文将从调度器的创建为入口,通过分析定时器中断以及PendSV中断的原理,刨析任务调度的本质原理。
任务调度整体框架
从上图可以看到,调度器是在main函数中初始化,同时初始化后会启动第一个任务函数;任务上下文切换实际也是借用了一个特殊的中断PendSV,此中断可以挂起,当有任务切换请求后,需要在没有更高优先级中断执行时才会执行PendSV。PendSV主要就是保存上文,切换到下文。
任务调度初始化源码分析
1.void vTaskStartScheduler( void )
函数实现流程及作用:
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
}
#else
{
xReturn = xTaskCreate( prvIdleTask,
"IDLE", configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle );
}
#endif
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
if( xReturn == pdPASS )
{
portDISABLE_INTERRUPTS();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) 0U;
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
if( xPortStartScheduler() != pdFALSE )
{
}
else
{
}
}
else
{
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
( void ) xIdleTaskHandle;
}
2.BaseType_t xPortStartScheduler( void )
函数的功能及实现如下:
BaseType_t xPortStartScheduler( void )
{
configASSERT( ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) );
#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
ulOriginalPriority = *pucFirstUserPriorityRegister;
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
prvSetupMPU();
prvSetupTimerInterrupt();
uxCriticalNesting = 0;
vPortEnableVFP();
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
prvStartFirstTask();
return 0;
}
启动第一个任务分析
在初始化任务调度时,最后会执行prvStartFirstTask()函数去启动第一个任务,此部分以cortex-m3为例;此部分是汇编实现
源码如下:
__asm void prvStartFirstTask( void )
{
PRESERVE8
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
msr msp, r0
cpsie i
cpsie f
dsb
isb
svc 0
nop
nop
}
调用SVC将会执行SVC中断函数:
SVC中断服务函数一开始就使用全局指针pxCurrentTCB获得第一个要启动的任务TCB,从而获得任务的当前堆栈栈顶指针。先将人为入栈的寄存器R4~R11出栈,将最新的堆栈栈顶指针赋值给线程堆栈指针PSP,再取消中断掩蔽。到这里,只要发生中断,就都能够被响应了;
执行bx r14指令后,硬件自动将寄存器xPSR、PC、LR、R12、R3~R0出栈,这时任务的任务函数指针vTask_func会出栈到PC指针中,从而开始执行任务函数。
至此,任务获得CPU执行权,调度器正式开始工作。
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
ldmia r0!, {r4-r11}
msr psp, r0
isb
mov r0, #0
msr basepri, r0
orr r14, #0xd
bx r14
}
对于Cortex-M3架构,需要依次入栈xPSR、PC、LR、R12、R3-R0、R11-R4,其中r11~R4需要人为入栈,其它寄存器由硬件自动入栈。寄存器PC被初始化为任务函数指针vTask_func,这样当某次任务切换后,任务A获得CPU控制权,任务函数vTask_func被出栈到PC寄存器,之后会执行任务的代码;LR寄存器初始化为函数指针prvTaskExitError,这是由移植层提供的一个出错处理函数。
PendSV中断
PendSV(可挂起系统调用)用于完成任务切换,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV会推迟执行,直到高优先级中断执行完毕。
当进行任务切换请求时,实际上就是将PendSV的悬起位置1,在没有其它中断运行时执行PendSV中断服务函数,在这个中断函数中实现任务切换。
#define taskYIELD() portYIELD()
#define portYIELD() \
{ \
\
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
\
\
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
1.实际在任务创建以及恢复时,会判断新加入就绪态的任务优先级是否大于当前执行的任务优先级,如果大于就会调用上述宏,进行中断请求;
2.针对中断内使用FromISR版本的任务恢复函数,实际是没有立马进行设置PendSV悬挂位,而是待退出中断后,判断标志,若需要请求任务切换,则设置PendSV悬挂位。
3.定时器systick中断轮询,每1ms轮询一次,判断是否满足任务切换的条件,如果满足,则请求任务切换。
systick定时中断
SysTick用于产生系统节拍时钟,提供一个时间片,如果多个任务共享同一个优先级,则每次SysTick中断,下一个任务将获得一个时间片。
初始化systick:
void vPortSetupTimerInterrupt( void )
{
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
中断函数:
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
if( xTaskIncrementTick() != pdFALSE )
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
通过上面即可看出,systick定时中断主要操作是在xTaskIncrementTick
函数中执行的,且如果执行后返回值有任务上下文的请求,则进行PendSV请求。
下面分析xTaskIncrementTick函数
函数流程如下:
根据上述的函数流程图,相信看到下述代码不成问题。
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
prvAddTaskToReadyList( pxTCB );
#if ( configUSE_PREEMPTION == 1 )
{
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
}
}
#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
#if ( configUSE_TICK_HOOK == 1 )
{
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
else
{
++uxPendedTicks;
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
return xSwitchRequired;
}
本文部分引用了网友的此篇博客,也十分感谢他的分享:FreeRTOS调度器启动过程分析.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)