关于msOS内核切换学习-汇编

关于msOS内核切换学习-汇编

说明

  上两个星期都在学习msOS,第一周看完了一本书,第二周看了相关的视频和认真分析代码。原计划是每一周做一次总结,结果每一周总有那么多事。这样我深切的认识到,不能把所有事都安排到周末,也不要太高估自己的执行能力。因此决定一边学习一边做相应笔记吧!

  msOS是我朋友公司老板自己编写的一个小型操作系统。具体msOS是什么这里不进行详细说明,学习msOS主要的目的是更好学习RTOS操作系统和学习设计过程中展现的智慧。接下来贴出OS的内核代码的任务切换部分。内核切换可以说是OS的核心,理解了这个之后,其他知识只是一个锦上添花,可以很好的一部分一部分学习。


目录


代码讲解

  • 任务栈定义代码
/*******************************************************************************
* 描述        : 初始化任务栈,xPSR、PC、R14、R12、R3、R2、R1、R0入栈顺序是由Cortex\
*           : 芯片决定的。中断入栈是芯片硬件自动完成,R11~R4顺序是由OS自己决定的。
* 输入参数  : taskPointer: 任务入口地址, stackRamTopPointer: 栈内存最高地址
* 返回参数  : 栈顶
*******************************************************************************/
static uint * InitStack(void (*taskPointer)(void), uint * stackPointer)
{
    *(stackPointer)   = (uint)0x01000000L;       // xPSR
    *(--stackPointer) = (uint)taskPointer;       // 任务入口地址
    *(--stackPointer) = (uint)0xFFFFFFFEL;       // R14 (LR)

    *(--stackPointer) = (uint)0x12121212L;       // R12
    *(--stackPointer) = (uint)0x03030303L;       // R3
    *(--stackPointer) = (uint)0x02020202L;       // R2
    *(--stackPointer) = (uint)0x01010101L;       // R1
    *(--stackPointer) = (uint)0x00000000L;       // R0 

    *(--stackPointer) = (uint)0x11111111L;       // R11
    *(--stackPointer) = (uint)0x10101010L;       // R10
    *(--stackPointer) = (uint)0x09090909L;       // R9
    *(--stackPointer) = (uint)0x08080808L;       // R8
    *(--stackPointer) = (uint)0x07070707L;       // R7
    *(--stackPointer) = (uint)0x06060606L;       // R6
    *(--stackPointer) = (uint)0x05050505L;       // R5
    *(--stackPointer) = (uint)0x04040404L;       // R4

    return(stackPointer);
}

栈的讲解

  • 栈的定义
      栈的本质就是一种数据结构类型,是一种只能单向操作的线性列表。访问栈只需要一个栈顶指针SP就可以了,栈底是固定不变的数值。在M3中有两个栈指针分别是MSP和PSP,都用同一个R13寄存器表示。MSP是用来指向系统的栈顶(main函数、系统异常、中断),PSP是用来指向用户任务栈顶指针。

  • 栈的应用
      由上面我们知道栈是一种数据结构,由于M3内核在运行函数时候存储形参和局部变量的时候存储方式和栈操作是一样的因此用来存放局部变量和形参的这块区域叫作栈。本质上栈是SRAM的一块存储区域。

注意:但函数调用完后或者函数切换时候会执行出栈操作,这样会使得sp栈顶会指向栈底。


  • 内核切换汇编代码
/*******************************************************************************
 - 描述       : 任务切换,在PendSV中断中实现
*******************************************************************************/                                                       
__asm void PendSV_Handler(void)
{
  IMPORT  CurrentTaskPointer
  IMPORT  NextTaskPointer

    CPSID   I                           // 关闭中断
    MRS     R0, PSP                     // 读取PSP到R0
    LDR     R2, =CurrentTaskPointer     // 获取指向当前任务指针的地址
    LDR     R3, =NextTaskPointer        // 获取指向新任务指针的地址
    CBZ     R0, NextTask                // 复位后,任务启动时,从中断模式跳转到任务中
    // 入栈                                    
    SUBS    R0, R0, #32                 // 栈顶减32,即8Word
    STM     R0, {R4-R11}                // 压栈R4~R11,8个寄存器
    LDR     R1, [R2]                    // 获取指向当前任务的指针,也指向第一成员StackPointer
    STR     R0, [R1]                    // 把当前栈顶存入表中第一个成员
    // 获取新栈顶
NextTask       
    LDR     R0, [R3]                    // 获取新任务指针变量值
    STR     R0, [R2]                    // 赋值给当前任务指针变量
    // 出栈
    LDR     R0, [R0]                    // 加载新任务栈顶到R0
    LDM     R0, {R4-R11}                // 加载栈内容到R4~R11
    ADDS    R0, R0, #32                 // 新栈顶升32,即8Word
    MSR     PSP, R0                     // 新栈顶写入PSP
    ORR     LR, LR, #0x04               // LR的bit2写入1,中断退出后进入任务模式,使用PSP栈
    CPSIE   I                           // 开中断
    BX      LR                          // 返回
    NOP
}

STM32寄存器学习

  通过代码我们可以看出来,任务的切换实质就是当前任务使用到的寄存器的备份,以及恢复下一次任务寄存器的数值,最后切换PSP(任务栈指针)的地址和PC地址。其中用到寄存器[R0-R15]、xPSR。
寄存器描述
  xPSR是特殊寄存器,用于存放程序运行状态用的。关于寄存器更多知识请查看Cortex M3权威指南

汇编语法知识学习

关键字列表

关键字            描述
__asm表示在C代码中插入汇编
IMPRORT声明标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
CPSIDPRIMASK写1 用来关闭中断
MRSMRS指令用于将程序状态寄存器的内容传送到通用寄存器中
LDR从存储器中加载字到一个寄存器中
CBZ比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令)
SUBSSUB减法操作 当没有S时指令不更新CPSR中条件标志位的值
STMSTM批量存储指令可以实现一组寄存器和一块连续的内存单元之间传输数据。
STR把寄存器的数值存储到内存单元
LDMLDM批量加载可以实现一组寄存器和一块连续的内存单元之间传输数据。
ADDSADD加法操作 当没有S时指令不更新CPSR中条件标志位的值
MSRMSR指令用亍将操作数的内容传送到程序状态寄存器的特定域中
ORR指令用于在两个操作数上进行逻辑或运算,通常用来置位
CPSIEPRIMASK写0 用来开启中断
BXBX指令跳转到指令中所指定的目标地址
NOP执行NOP指令只使程序计数器PC加1,所以占用一个机器周期

重点讲解

  • MRS  R0,   PSP    // 读取PSP到R0

Cortex M3权威指南引用

注意:因为在进入PendSV_Handler后我们使用的是MSP,因此需要使用MRS指令来进行访问PSP。由于xPSR、PC、R14、R12、R3、R2、R1、R0是芯片自动入栈,入栈顺序是按照InitStack定义寄存器顺序。所以读取到PSP指向是R0位置,即当前任务栈中第八个成员。

  • CBZ  R0,   NextTask   // 复位后,任务启动时,从中断模式跳转到任务中

因为在一开始初始化的时候把PSP设置为零,这句操作是为了判断是否为第一次进入任务切换操作,如果是就跳过R4-R11的备份,反之亦然。

  • SUBS  R0,   R0,   #32   // 栈顶减32,即8Word
  • STM   R0,   {R4-R11}   // 压栈R4~R11,8个寄存器

因为是入栈操作所以地址需要减小,至于为什么要先减地址后做数据备份,这个和STM指令操作方式有关。M3内核栈操作方式是,栈顶为最高地址,入栈地址要减小,出栈地址要增加。
注意:R0这时刻存放的是当前任务栈顶指针。这里使用到的LDM和STM都是先自增4字节地址后赋值。

  • LDR  R1,   [R2]    // 获取指向当前任务的指针,也指向第一成员StackPointer
  • STR   R0,   [R1]  // 把当前栈顶存入表中第一个成员

主要是把当前任务的栈顶指针保存到,当前任务栈中的第一个成员。R0中存放的是栈顶指针。

  • LDR  R0,   [R3]  // 获取新任务指针变量值
  • STR  R0,   [R2]  // 赋值给当前任务指针变量

把下一个任务的指针赋值给当前任务。即两个任务指针变量相等。

  • LDR  R0,   [R0]  // 加载新任务栈顶到R0
  • LDM  R0,   {R4-R11}   // 加载栈内容到R4~R11
  • ADDS  R0,   R0,   #32   // 新栈顶升32,即8Word

由上面入栈代码可以知道,任务栈指针中实际存放的数值是栈顶指针,因此通过上面段代码第一句操作可以得到下一个任务的栈顶,通过LDM操作加载栈中备份R4-R11数值恢复到寄存器中,因为这是出栈操作,所以地址需要增加。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

照君明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值