FreeRTOS笔记(三):任务调度器
调度器是 FreeRTOS 操作系统的核心,主要负责任务切换,即找出最高优先级的就绪任务,并使之获得 CPU 运行权。调度器并非自动运行的,需要人为启动它。
使用场景如下:
int main(void)
{
//....
//创建任务
vTaskStartScheduler(); //开启任务调度器
}
一、任务调度器开启函数
创建任务之后,就会调用vTaskStartScheduler()函数开启调度器,具体函数如下:
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
xReturn = xTaskCreate( prvIdleTask, //-----------------------------------(1)
"IDLE", configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle );
#if ( configUSE_TIMERS == 1 ) //使能定时器
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask(); //----------------------------(2)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS ) //空闲任务和定时器任务创建成功
{
portDISABLE_INTERRUPTS(); //----------------------------(3)
#if ( configUSE_NEWLIB_REENTRANT == 1 ) //使能NEWLIB
{
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE; //--------------------------------(4)
xTickCount = ( TickType_t ) 0U;
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); //-------------------(5)
if( xPortStartScheduler() != pdFALSE ) //---------------------(6)
{
//任务调度器启动成功不会运行到这
}
else
{
//不会运行到这
}
}
else
{
//创建空闲任务或定时器任务失败,导致启动失败
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
//防止编译出错
( void ) xIdleTaskHandle;
}
分析如下:
(1)创建空闲任务;
(2)使能软件定时器,并创建定时器任务;
(3)关闭中断,在SVC中断函数中会打开中断;
(4)xSchedulerRunning设置为真,表示调度器开启;
(5) 如果宏configGENERATE_RUN_TIME_STATS被定义,表示使用运行时间统计功能,用于初始化一个基础定时器/计数器;
(6)调用xPortStartScheduler来初始化跟调度器有关的硬件,比如滴答定时器与PendSV。具体将在一下节介绍。
二、内核相关硬件初始化函数分析
FreeRTOS系统时钟由滴答定时器来提供,而且任务切换也会用到PendSV中断,这些硬件都是由xPortStartScheduler()函数完成。
BaseType_t xPortStartScheduler( void )
{
//省略了条件编译
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; //------------------(1)
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; //------------------(2)
vPortSetupTimerInterrupt(); //------------------(3)
uxCriticalNesting = 0; //------------------(4)
prvStartFirstTask(); //------------------(5)
return 0;
}
(1)设置PendSV中断优先级,为最低优先级;
(2)设置滴答定时器中断优先级,为最低优先级;
(3)设定滴答定时器周期以及使能滴答定时器的中断;
(4)初始化临界区嵌套计数器;
(5)调用prvStartFirstTask()开启第一个任务。将在下节介绍
补充:
在 Cortex-M3 架构中,FreeRTOS 为了任务启动和任务切换使用了三个异常:SVC、PendSV 和 SysTick。
●SVC(系统服务调用)用于任务启动,有些操作系统不允许应用程序直接访问硬件,而是通过提供一些系统服务函数,通过 SVC 来调用;
●PendSV(可挂起系统调用)用于完成任务切换,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV 会推迟执行,直到高优先级中断执行完毕;
●SysTick 用于产生系统节拍时钟,提供一个时间片,如果多个任务共享同一个优先级,则每次 SysTick 中断,下一个任务将获得一个时间片。
三、启动第一个任务
硬件初始化的函数最后一步就是开启第一个任务,调用函数prvStartFirstTask()完成,这是一个汇编函数,如下:
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Cortext-M3硬件中,0xE000ED08地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/
ldr r0, =0xE000ED08
ldr r0, [r0]
/* 取出向量表中的第一项,向量表第一项存储主堆栈指针MSP的初始值*/
ldr r0, [r0]
/* 将堆栈地址存入主堆栈指针 */
msr msp, r0
/* 使能全局中断*/
cpsie i
cpsie f
dsb
isb
svc 0 //触发SVC中断开启第一个任务
nop
nop
}
四、SVC中断函数
在函数prvStartFirstTask中通过调用SVC指令触发了SVC中断,第一个任务的启动就是在SCV中断服务函数中完成的。
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB //---------------------(1)
ldr r1, [r3] //---------------------(2)
ldr r0, [r1] //---------------------(3)
ldmia r0!, {r4-r11,r14} //---------------------(4)
msr psp, r0 //---------------------(5)
isb
mov r0, #0
msr basepri, r0 //---------------------(6)
orrr14, #0xd //---------------------(7)
bx r14 //---------------------(8)
}
(1) pxCurrentTCB指向处于最高优先级的就绪任务TCB ;
(2) 获取任务TCB地址 ;
(3) 获取任务TCB的第一个成员,即当前堆栈栈顶pxTopOfStack ;
(4) 将寄存器r4~r11和r14出栈 ;
(5) 进程堆栈指针PSP设置成任务堆栈;
(6) 打开中断;
(7) CPU退出中断,进入线程模式并使用线程栈;
(8) 执行此代码后,任务调度正式成功,第一个任务开始运行。
参考资料
https://blog.csdn.net/qq_27114397/article/details/83017422