开始调度
FreeRTOS使用vTaskStartScheduler来启动调度器。 流程如下:
创建空闲任务
创建一个优先级为0的IDLE任务,保证至少有一个任务在运行。
关中断
为后面配置systick中断准备
初始化所需变量
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
xPortStartScheduler
配置systick,PendSV等中断。
prvStartFirstTask
开启第一个任务。
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Cortext-M3硬件中,0xE000ED08 地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/
/* 将 0xE000ED08 加载到 R0 */
ldr r0, =0xE000ED08
/* 将 0xE000ED08 中的值,也就是向量表的实际地址加载到 R0 */
ldr r0, [r0]
/* 根据向量表实际存储地址,取出向量表中的第一项,向量表第一项存储主堆栈指针MSP的初始值*/
ldr r0, [r0]
/* 将堆栈地址写入主堆栈指针 */
msr msp, r0
/* 使能全局中断*/
cpsie i
cpsie f
dsb
isb
/* 调用SVC启动第一个任务 */
svc 0
nop
nop
}
先拿到MSP的地址,然后把初始的主堆栈地址写入。这一步破坏了之前的堆栈,使代码再也无法回到原来的函数。然后开中断,手动拉一个svc中断,进入SVC_ISR。
vPortSVCHandler
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB /* pxCurrentTCB指向处于最高优先级的就绪任务TCB */
ldr r1, [r3] /* 获取任务TCB地址 */
ldr r0, [r1] /* 获取任务TCB的第一个成员,即当前堆栈栈顶pxTopOfStack */
ldmia r0!, {r4-r11} /* 出栈,将寄存器r4~r11出栈 */
msr psp, r0 /* 最新的栈顶指针赋给线程堆栈指针PSP */
isb
mov r0, #0
msr basepri, r0
orr r14, #0xd /* 这里0x0d表示:返回后进入线程模式,从进程堆栈中做出栈操作,返回Thumb状态*/
bx r14
}
拉了svc之后,就会进入vPortSVCHandler。这里先获取了处于最高优先级的就绪任务的TCB,然后把PSP指向这个任务的堆栈,返回进入线程模式。
任务开始运行
中断退出,CPU把任务创建时放到堆栈的数据(参照任务创建 初始化堆栈)出栈,开始调用任务函数,同时传入参数,任务开始。