• μC/OS-II 要点分析 ------ PendSV_Handler


    首先贴出今天要与大家分享的内容源码(位于内核源码的 os_cpu_a.asm 中):

    PendSV_Handler
      CPSID I
      MRS R0, PSP
      CBZ R0, PendSV_Handler_Nosave

      SUBS R0, R0, #0x20
      STM R0, {R4-R11}

      LDR R1, =OSTCBCur
      LDR R1, [R1]
      STR R0, [R1]

    PendSV_Handler_Nosave
      PUSH {R14}
      LDR R0, =OSTaskSwHook
      BLX R0
      POP {R14}

      LDR R0, =OSPrioCur
      LDR R1, =OSPrioHighRdy
      LDRB R2, [R1]
      STRB R2, [R0]

      LDR R0, =OSTCBCur
      LDR R1, =OSTCBHighRdy
      LDR R2, [R1]
      STR R2, [R0]

      LDR R0, [R2]
      LDM R0, {R4-R11}
      ADDS R0, R0, #0x20
      MSR PSP, R0
      ORR LR, LR, #0x04
      CPSIE I
      BX LR

    这两段代码尤为重要,内核中任务的切换主要就是由它们实现的。接下来,我将逐行为大家解析其中的奥秘。

    这两段代码是中断服务程序(ISR),那么由谁来触发中断呢?我们以OSCtxSw()这个函数为入手点。
    它其实就是C程序中的OS_TASK_SW()

    #define OS_TASK_SW() OSCtxSw()

    开始分析OSCtxSw()

    OSCtxSw
      PUSH {R4, R5}
      LDR R4, =NVIC_INT_CTRL
      LDR R5, =NVIC_PENDSVSET
      STR R5, [R4]
      POP {R4, R5}
      BX LR

    1.首先进入函数,将R4,R5入栈,保护寄存器。
    2.给R4,R5分别赋值,文件中这样定义:
    NVIC_INT_CTRL EQU 0xE000ED04
    NVIC_PENDSVSET EQU 0x10000000
    NVIC_INT_CTRL为中断控制寄存器的地址,NVIC_PENDSVSET为PendSV中断的触发值
    3.将触发值写入控制寄存器
    4.弹出R4,R5


    因为我们一般调用任务切换的时候,都是在临界区调用(禁止中断),所以不会产生中断。之后调用OS_EXIT_CRITICAL()函数退出临界区后,中断才会发生,这样就产生了PendSV异常。

    接下来才是我们的重点,开始分析ISR!

    CPSID I
    #关中断,防止切换任务期间被打扰

    MRS R0, PSP
    #取出PSP(程序栈指针)赋值给R0

    CBZ R0, PendSV_Handler_Nosave
    #若R0为0,则跳转到PendSV_Handler_Nosave函数继续执行,这里我们讲述的就是由延时导致的任务切换,PSP都是有值的,所以继续执行

    SUBS R0, R0, #0x20
    #将R0 - 0x20 则R0与PSP之间空出8个单位(每个单位4个字节)

    STM R0, {R4-R11}
    #将寄存器的R4-R11存入空出的8个单位

    LDR R1, =OSTCBCur
    #将当前TCB的地址赋给R1

    LDR R1, [R1]
    #取出地址处的数据(即OSTCBCur->SP的地址,因为OSTCB结构体的第一个数据就是SP)赋给R1

    STR R0, [R1]
    #将R0(栈顶数据的地址)赋值给OSTCBCur->SP,则OSTCBCur->SP与R0指向的位置相同

    !!这段程序的OSTCBCur指的都是old_task的指针

    这段程序的图解:

     

    PUSH {R14}
    LDR R0, =OSTaskSwHook
    BLX R0
    POP {R14}
    #这小段代码就是执行OSTaskSwHook这个C的函数

    LDR R0, =OSPrioCur
    LDR R1, =OSPrioHighRdy
    LDRB R2, [R1]
    STRB R2, [R0]
    #这小段代码就将OSPrioHighRdy赋给OSPrioCur

    LDR R0, =OSTCBCur
    LDR R1, =OSTCBHighRdy
    LDR R2, [R1]
    STR R2, [R0]
    #这小段代码就将OSTCBHighRdy->SP赋给OSPrioCur->SP

    LDR R0, [R2]
    #将OSTCBHighRdy->SP的值(指向新任务栈中的地址,也就是新任务的栈顶)赋值给R0

    LDM R0, {R4-R11}
    #以R0为基址,读出8个单位的数据给CPU的寄存器

    ADDS R0, R0, #0x20
    #R0地址 + 0x20,指向几个重要的寄存器值(当前栈顶)

    MSR PSP, R0
    #将当前R0的值赋值给PSP

    ORR LR, LR, #0x04
    #确保异常返回后,新任务使用PSP指针

    CPSIE I
    #开启中断

    !!这段程序的OSTCBCur指的都是new_task的指针

    这段程序的图解:

    这样也就完成了old_task到new_task的切换!

    感谢花费宝贵的时间浏览, 转载请注明出处。 本人将在[资源共享]分类下陆续加入学习过程中一些比较重要且有用处的资料、源码,大家可前往下载,一起进步。 感谢支持!
  • 相关阅读:
    容器技术问题
    URL和URL
    容器和注入技术
    云计算应用开发与实践读书 笔记(三)
    云计算应用开发与实践读书 笔记 (二 )
    云计算应用开发与实践读书 笔记
    C++学习之DLL注入
    c语言学习,模拟栈操作
    领悟百分比定位
    强大的第三方工具autoPrefixer
  • 原文地址:https://www.cnblogs.com/GyForever1004/p/8318860.html
Copyright © 2020-2023  润新知