zoukankan      html  css  js  c++  java
  • 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
    }
  • 相关阅读:
    元宇宙的特点
    Meta Network
    Decentraland
    Cryptovoxel
    The Sandbox Game
    Roblox
    JAVA参数传递
    静态方法使用@Autowired注入写法
    mysql索引
    Java中锁的分类
  • 原文地址:https://www.cnblogs.com/uestcliming666/p/12411885.html
Copyright © 2011-2022 走看看