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

一,前言

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的学习笔记)