FreeRTOS - 调度器
指导任务的创建和删除后,那么多个任务之间是如何切换的呢。这些功能都是由调度器完成的。接下来就研究下调度器的原理。
vTaskStartScheduler
创建完任务之后,就开启调度器。
创建一个idle task,因为如果所有的任务都没有运行,总要有一个任务要运行,所以需要一个idle task。优先级最低。
xReturn = prvCreateIdleTasks();
中断在这里是关闭的,以确保在调用xPortStartScheduler ( 之前或期间不会出现勾选:创建的任务的堆栈包含一个状态字,中断处于打开状态,因此当第一个任务开始运行时,中断将自动重新启用。
portDISABLE_INTERRUPTS();
任务还没正常开始调度,所以delay设置为最大。调度器的运行状态设置成true。tickCout从0开始。
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
xPortStartScheduler
然后就开始启动调度器,这个函数不会返回,除非遇到错误。这个函数和具体的硬件有关系。
我们以ARM CortexM3为例。
设置PendSV和SysTick为最低优先级。为什么优先级最低和为什么是这两个中断后面再分析。
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
我们只需要知道这里设置了两个中断。
SysTick中断设置完优先级后,需要配置timer,例如周期。
__attribute__( ( weak ) ) void vPortSetupTimerInterrupt( void )
{
/* Calculate the constants required to configure the tick interrupt. */
#if ( configUSE_TICKLESS_IDLE == 1 )
{
ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
}
#endif /* configUSE_TICKLESS_IDLE */
/* Stop and clear the SysTick. */
portNVIC_SYSTICK_CTRL_REG = 0UL;
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
/* Configure SysTick to interrupt at the requested rate. */
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
先停止SysTick,count清0。然后更具用户配置来设置SysTick中断的周期。也就是task调度的周期。我们这里不用管具体的硬件是如何配置的,只需要知道原理。需要一个定时的中断函数类触发任务切换。
设置完systick后就可以开始第一个任务了。
/* Start the first task. */
static void prvPortStartFirstTask( void )
{
__asm volatile (
" ldr r0, =0xE000ED08 \n" /* Use the NVIC offset register to locate the stack. */
" ldr r0, [r0] \n"
" ldr r0, [r0] \n"
" msr msp, r0 \n" /* Set the msp back to the start of the stack. */
" cpsie i \n" /* Globally enable interrupts. */
" cpsie f \n"
" dsb \n"
" isb \n"
" svc 0 \n" /* System call to start first task. */
" nop \n"
" .ltorg \n"
);
}
- 从0xE000ED08(硬件寄存器)处获取中断向量表起始地址,默认是0x80000000。然后从中断向量表的起始地址获取主栈指针MSP的初始值,所以汇编的前三行就是为了获取MSP的初始值。
- 打开中断。
- 调用svc指针触发svc中断。也就是PendSV中断。
SVC中断异常函数如下:
void vPortSVCHandler( void )
{
__asm volatile (
" ldr r3, pxCurrentTCBConst2 \n" /* Restore the context. */
" ldr r1, [r3] \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" ldmia r0!, {r4-r11} \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
" msr psp, r0 \n" /* Restore the task stack pointer. */
" isb \n"
" mov r0, #0 \n"
" msr basepri, r0 \n"
" orr r14, #0xd \n"
" bx r14 \n"
" \n"
" .align 4 \n"
"pxCurrentTCBConst2: .word pxCurrentTCB \n"
);
}
获取当前要运行的任务,然后回去栈顶指针。从栈顶指针里恢复寄存器的值。所以这就是为什么pxTopOfStack必须是tcb的第一个元素。
原文地址:https://blog.csdn.net/u012701675/article/details/136056239
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!