开始调度


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把任务创建时放到堆栈的数据(参照任务创建 初始化堆栈)出栈,开始调用任务函数,同时传入参数,任务开始。