美文网首页RTOS和GUI_基于英飞凌tc2x及stm32开发板
FreeRTOS任务切换源码分析--Apple的学习笔记

FreeRTOS任务切换源码分析--Apple的学习笔记

作者: applecai | 来源:发表于2021-04-16 20:58 被阅读0次

    一,前言

    RTOS中最吸引我的地方是带汇编的任务切换,没想到我在看port.c,全部看完后,有一个xPortPendSVHandler函数觉得理解的不太清晰,但是以前我肯定理解过的,所以呢,我又调试了下,等于再复习下。

    二,xPortPendSVHandler源码分析

    1. 先做过铺垫,来看下什么时候会调用xPortPendSVHandler中断函数。
      任务时间片切换#define xPortSysTickHandler SysTick_Handler,xPortSysTickHandler函数就是一个中断。在启动第一个高优先级任务前,vPortSetupTimerInterrupt函数中已经设置了心跳包的频率。portNVIC_SYSTICK_LOAD_REG = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
      #define configTICK_RATE_HZ ( ( TickType_t ) 500 )说明是500Hz,1/500=0.002就是2ms的一个心跳中断。
      心跳中断中的函数中portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;就是请求pendsv中断,并增加tick值,检查任务就绪队列中是否有任务,存在任务则请求pendsv进行任务切换。如下c代码还是很好理解的。
    void xPortSysTickHandler( void )
    {
        uint32_t ulPreviousMask;
    
        ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
        {
            /* Increment the RTOS tick. */
            if( xTaskIncrementTick() != pdFALSE )
            {
                /* Pend a context switch. */
                portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
            }
        }
        portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
    }
    

    那么就说明是心跳时钟切换过程中通过portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;来设置pendsv中断请求,之后才进入xPortPendSVHandler中断函数。
    2. xPortPendSVHandler源码分析
    中文注释我已经直接写在函数中了。如下,这都是我经过debug单步调试验证过的。一开始没有看懂,主要是stmdb用于将寄存器压栈,ldmia用于将寄存器弹出栈等几个汇编指令忘记了含义。

    __asm void xPortPendSVHandler( void )
    {
        extern vTaskSwitchContext
        extern pxCurrentTCB
    
    /* *INDENT-OFF* */
        PRESERVE8
    
        mrs r0, psp
    
        ldr r3, = pxCurrentTCB /* Get the location of the current TCB. */
        ldr r2, [ r3 ]
    /* 保存现场,主要保存PSP中的r4~r11 */
        subs r0, # 32  /* Make space for the remaining low registers. */
        str r0, [ r2 ] /* Save the new top of stack. */
    stmia r0 !, { r4 - r7 } /* Store the low registers that are not saved automatically. */
    /* 因为thumb指令stmia只能访问r0~r7 ,所以下面r8~r11先保存到r4~r7,然后再push到psp栈中,等于腾出32自己(8个寄存器的地址空间)先push r4~r7再push r8~r11 */
        mov r4, r8 /* Store the high registers. */
        mov r5, r9
        mov r6, r10
        mov r7, r11
        stmia r0 !, { r4 - r7 }
    /* 执行vTaskSwitchContext 函数前tcb和lr入栈保护 */
        push { r3, r14 }
        cpsid i
        bl vTaskSwitchContext
    cpsie i
    /* 恢复tcb和lr */s
        pop { r2, r3 } /* lr goes in r3. r2 now holds tcb pointer. */
        /* r2地址就是tcb指针的存储位置保存到r1 */
    ldr r1, [ r2 ]
    /* 将tcb内容保存到r0,tcb的内容就是通过vTaskSwitchContext运行后冲裁出待切换的tcb地址 */
    ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
    /* 从r0栈顶地址+16,腾出4个寄存器r4~r7的空间 */
    adds r0, # 16  /* Move to the high registers. */
    /* 将+16~+32地址中的内容先pop到r4~r7然后移动入r8~r11 */
    ldmia r0 !, { r4 - r7 } /* Pop the high registers. */
        mov r8, r4
        mov r9, r5
        mov r10, r6
        mov r11, r7
    
        /* 临时保存下r0地址,就是栈底的地址,会保存到PSP,等bx从中断退出时候会用到*/
    msr psp, r0   /* Remember the new top of stack for the task. */
        /* r0从最开始预留16以及pop后自动增加16,总共加了32,现在前去32,等于还原到栈顶,目的是之后用来恢复r4~r11 */
    subs r0, # 32 /* Go back for the low registers that are not automatically restored. */
    /* 然后pop r4~r7,之前popr8~r11的原因就是16 bit thumb指令只能pop r0~r7,不能直接pop 8个寄存器*/
        ldmia r0 !, { r4 - r7 } /* Pop low registers.  */
        /* 通过r3来查找返回地址,用来切换任务,返回到的是另外一个更高优先级的task函数*/
        bx r3
        ALIGN
    /* *INDENT-ON* */
    }
    

    几个关键语句的调试步骤调试截图如下
    stmia r0 !, { r4 - r7 }运行前。

    image.png
    stmia r0 !, { r4 - r7 }运行后。
    将寄存器r4到r7的值依次赋值给r0指定的地址单元(0x18000460),每次赋值一次r0就加4。4个寄存器赋值完成后,R0内容(就是地址)增加了4x4=16,变成了0x18000470。
    image.png
    push { r3, r14 }含义为MSP入栈保护MSP地址为0x180006e8,然后将r3和r14保存到这个地址进行入栈
    image.png
    入栈地址是做减法,所以先查看0x180006e0地址的内容。
    运行push { r3, r14 }后0x180006e0和0x180006e4地址内容保存了r3和r14的内容。进行了入栈操作。
    image.png
    同理pop { r2, r3 }是出栈,将r3(tcb地址)和lr通过出栈,保存到r2和r3。
    image.png
    adds r0, # 16运行前
    image.png
    adds r0, # 16运行后,r0地址变更。
    image.png
    4个mov后将0x180001D0后面的四个字节内容(就是自己构造栈空间中最后4个字节代表r8~r11)进行了pop。
    image.png
    从中断退出时候会用到。
    subs r0, # 32 的含义是,r0从最开始预留16以及pop后自动增加16,总共加了32,现在前去32,等于还原到栈顶。
    ldmia r0 !, { r4 - r7 } ,之前已经pop过r8~r11,现在就是pop r4r7,之前popr8r11的原因就是16 bit thumb指令只能pop r0~r7,不能直接pop这8个寄存器
    image.png
    bx r3就是跳入0xFFFFFFFD,说明被中断前用的是PSP的地址。0xFFFFFFF9说明用的是MSP的地址。
    所以找0x180001e0的地址。要从svc中断返回的地址PC保存在0x180001e0+7*4=0x180001fc的内容,就是0x1100150D,但是thumb指令需要LSB,所以0x1100150D-1就是0x1100150C地址。bx r3从中断出来后,会跳入0x1100150C执行task。
    image.png
    msr psp, r0是保存r0的地址到PSP栈中,目的是把栈底保存,等bx
    image.png

    三,小结

    通过调试及查看cortex内核手册复习了下内核寄存器的用法,这个函数就很容易理解了,看来有空我要重新看下cortexM内核手册的全部内容。

    相关文章

      网友评论

        本文标题:FreeRTOS任务切换源码分析--Apple的学习笔记

        本文链接:https://www.haomeiwen.com/subject/ndcylltx.html