FreeRTOS任务创建、启动调度器、任务切换的过程分析——基于ARM-CotexM3

ARM-CM3创建任务、开启调度器、任务调度的整个流程:

【创建任务】

  1. 创建任务控制块。为任务申请空间并创建一个任务控制块NewTCB;
  2. 申请任务栈空间。为任务申请一块栈空间,并将起始地址存储到NewTCB.pxStack中;
  3. 初始化任务相关参数。将任务名、优先级和相关列表项等存放到任务控制块;
  4. 初始化任务栈中的上下文。计算栈顶指针pxTopOfStack,并通过该指针初始化上下文堆栈,主要包括xPSR、PC、LR,并给其他上下文留空,最后将栈顶指针pxTopOfStack存储到任务控制块TCB中。其中PC初始化为任务函数指针pxCode;
  5. 将新创建的任务加入任务就绪列表;

【启动任务调度器】

  1. 开启PendSV和Systick中断;
  2. 启动第一个任务。找到主栈的起始地址赋给MSP寄存器,使能中断并触发SVC服务来完成第一个任务的启动。SVC服务完成以下工作:(1)首先从TCB中获取第一个任务的栈顶指针,然后从栈顶开始恢复r11-r4寄存器;(2)将此时的栈顶指针赋给PSP寄存器供系统自动恢复其余上下文使用;(3)开启中断;(4)将r14(保存返回地址)或上0x0D,即设置返回时进入线程模式,从而在自动恢复上下文时使用PSP(ARM-CotexM3的堆栈指针分为主栈指针MSP和进程栈指针PSP);(5)最后执行<bx r14>从PSP处恢复其余上下文并返回,然后执行PC指向的任务函数;

【进行任务切换】

  1. 任务切换有两种场合:执行系统调用和触发SysTick中断,但最终都是依靠PendSV中断来实现。当发生任务切换时,进入PendSV中断,进入中断前已自动保存一部分上下文,从PSP指向地址开始入栈。进入中断后首先读取PSP到r0寄存器用于手动入栈操作,此时PSP应该指向另一部分上下文的起始地址,然后将另一部分上下文入栈保存,最后将此时的r0存储到任务控制块的栈顶指针成员即pxNewTCB->pxTopOfStack,至此完成了当前任务的上下文保存。接下来进行任务的切换,首先将r3和r14入主栈保存,防止调用vTaskSwitchContext函数时被覆盖,因为r14中保存了PendSV中断的返回地址;且当任务切换函数vTaskSwitchContext执行完成后,pxCurrentTCB被更新,而r3中保存了变量pxCurrentTCB的地址,因此可以继续使用r3访问它。找到新的任务后,要想使其运行,需要将其上下文出栈恢复,因此与启动第一个任务一样,从pxCurrentTCB中保存的任务栈顶指针处先恢复下文,然后将此时的栈顶指针赋给PSP,然后执行返回指令,从而自动恢复其他上下文并跳转至PC所指向的指令地址——任务函数地址。

关键函数代码如下:

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    /* Simulate the stack frame as it would be created by a context switch
    interrupt. */
    pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;    /* xPSR */
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;    /* PC */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) prvTaskExitError;    /* LR */

    pxTopOfStack -= 5;    /* R12, R3, R2 and R1. */
    *pxTopOfStack = ( StackType_t ) pvParameters;    /* R0 */
    pxTopOfStack -= 8;    /* R11, R10, R9, R8, R7, R6, R5 and R4. */

    return pxTopOfStack;
}

BaseType_t xPortStartScheduler( void )
{
/* Make PendSV and SysTick the lowest priority interrupts. */
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

    /* Start the timer that generates the tick ISR.  Interrupts are disabled
    here already. */
    vPortSetupTimerInterrupt();

    /* Initialise the critical nesting count ready for the first task. */
    uxCriticalNesting = 0;

    /* Start the first task. */
    prvStartFirstTask();

    /* Should not get here! */
    return 0;
}

__asm void prvStartFirstTask( void )
{
    PRESERVE8

    /* Use the NVIC offset register to locate the stack. */
    ldr r0, =0xE000ED08
    ldr r0, [r0]
    ldr r0, [r0]

    /* Set the msp back to the start of the stack. */
    msr msp, r0
    /* Globally enable interrupts. */
    cpsie i
    cpsie f
    dsb
    isb
    /* Call SVC to start the first task. */
    svc 0
    nop
    nop
}

__asm void vPortSVCHandler( void )
{
    PRESERVE8

    ldr    r3, =pxCurrentTCB    /* Restore the context. */
    ldr r1, [r3]            /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
    ldr r0, [r1]            /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0!, {r4-r11}        /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
    msr psp, r0                /* Restore the task stack pointer. */
    isb
    mov r0, #0
    msr    basepri, r0
    orr r14, #0xd
    bx r14
}

__asm void xPortPendSVHandler( void )
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;

    PRESERVE8

    mrs r0, psp
    isb

    ldr    r3, =pxCurrentTCB        /* Get the location of the current TCB. */
    ldr    r2, [r3]

    stmdb r0!, {r4-r11}            /* Save the remaining registers. */
    str r0, [r2]                /* Save the new top of stack into the first member of the TCB. */
    stmdb sp!, {r3, r14}
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    dsb
    isb
    bl vTaskSwitchContext
    mov r0, #0
    msr basepri, r0
    ldmia sp!, {r3, r14}

    ldr r1, [r3]
    ldr r0, [r1]                /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0!, {r4-r11}            /* Pop the registers and the critical nesting count. */
    msr psp, r0
    isb
    bx r14
    nop
}

 

posted @ 2020-03-04 20:17  凉风SK  阅读(762)  评论(0编辑  收藏  举报