uC/OS-II 要点分析 ------ PendSV_Handler

Posted Zackary.Liu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uC/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的切换!

以上是关于uC/OS-II 要点分析 ------ PendSV_Handler的主要内容,如果未能解决你的问题,请参考以下文章

uC/OS-II之系统函数20160526

uC/OS-II配置文件

uC/OS-II类型定义

uC/OS-II汇编代码

uC/OS-II测试(TEST)块

uC/OS-II标志(flag)块