指令、LDR伪指令、[Rn]寄存器间接引用 详细解析"/>
RTOS系列(13):汇编LDR指令、LDR伪指令、[Rn]寄存器间接引用 详细解析
RTOS系列(1):基础知识——中断嵌套
RTOS系列文章(2):PendSV功能,为什么需要PendSV
RTOS系列文章(3): 为什么将SysTick和PendSV的优先级设置为最低
RTOS系列文章(4): MDK软件仿真 + Debug-(printf)-Viewer使用方法
RTOS系列文章(5):C语言程序运行原理分析:汇编、栈、栈帧、进栈、出栈、保存现场、恢复现场、返回
RTOS系列文章(6):Cortex-M3/4之SP,MSP,PSP,Thread模式、Handler模式、内核态、用户态
RTOS系列文章(7):CM3/4之LR寄存器、EXC_RETURN深入分析
RTOS系列文章(8):深入分析中断处理过程
RTOS系列文章(9):再次分析栈帧、函数调用与中断调用的区别
RTOS系列文章(10):简单OS示例分析
RTOS系列文章(11):RTOS启动方式——直接设置CONTROL寄存器、SVC启动、PendSV启动
RTOS系列(12):使用SVC或PendSV启动OS流程详细分析
前言
汇编LDR指令在RTOS中使用的比较频繁,尤其是在PendSV中进行上下文切换的时候,LDR指令是不可缺少的,我们在看uC/OS、FreeRTOS、RT-Thread的源码时,都能够看到LDR的身影。由于LDR指令有2个名字:LDR指令、LDR伪指令,这就给我们理解LDR带来了困难,很容易混淆。本文就详细分析一下汇编LDR、[Rn]间接引用的原理。
Rn和[Rn] 的区别
[Rn] 表示间接寻址,类似指针, 在汇编指令中, Rn和[Rn]的区别就是,前者Rn操作是直接取寄存器Rn中的值,[Rn]则是将寄存器Rn存放的值认为是一个地址,然后取这个地址中的值。
LDR指令、LDR伪指令
LDR指令和LDR伪指令在使用的时候虽然都是使用LDR,但其实不是一个东西,其实也很容易区别,LDR后面带 ‘=’ 的就是LDR伪指令,其他则是LDR指令。
LDR伪指令的设计目的是为了把一个32位立即数或内存地址存入到一个寄存器。CPU在遇到LDR伪指令时,会判断这个32位立即数或地址,来决定是使用MOV指令,还是使用LDR 指令。
LDR指令
LDR R0, 0x12345678 ;把0x12345678这个地址中的值存放到r0中。而mov不能干这个活,mov只能在寄存器之间移动数据,;或者把立即数移动到寄存器中。
LDR R0,R1 ;表示把r1寄存器中的值放入r0
LDR R0,[R1] ; [R1]表示R1中值对应内存的地址,所以这里是把R1中的数当作一个地址,把这个地址指向的内存中的值放入R0.; 所以[R1] 相当于间接引用,类似C语言的指针,通过指针来取数。
LDR伪指令
LDR R0, =0x12345678 ;把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的。只不过mov指令限制了立即数的长度为8位,;也就是不能超过512。而ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,;那么在实际汇编的时候该ldr伪指令是被转换为mov指令的
LDR R0, =_start ; 将指定标号的值赋给r0, 这里取得的是标号 _start 的绝对地址,这个绝对地址(链接地址)是在链接的时候确定的。;它要占用 2 个 32bit的空间,一条是指令,另一条是文字池中存放_start 的绝对地址。
LDR指令、伪指令使用举例分析
#include "led.h"
#include "delay.h"
#include "sys.h"
#include <stdio.h>typedef struct tcb{unsigned int *pstk;u16 a;u16 b;
} tcb_t;static unsigned int currStack[32];
static tcb_t currTCB;
volatile tcb_t *pCurrTCB = NULL;__asm void asm_func(void)
{extern pCurrTCBPRESERVE8 ldr r3, =pCurrTCB /* [1] */ldr r1, [r3] /* [2] */ldr r0, [r1] /* [3] */ldmia r0, {r4-r11} /* */nop
}int main(void)
{ currTCB.pstk = &currStack[31];currTCB.a = 0x0102;currTCB.b = 0x0304;currStack[31] = 11;currStack[30] = 10;currStack[29] = 9;currStack[28] = 8;currStack[27] = 7;currStack[26] = 6;currStack[25] = 5;currStack[24] = 4;pCurrTCB = &currTCB;printf("currStack[31] addr:0x%X\n", &currStack[31]);printf("currTCB's addr:0x%X\n", &currTCB);printf("pCurrTCB's addr:0x%X\n", &pCurrTCB);printf("pCurrTCB's addr:0x%X\n", pCurrTCB);asm_func();return 0;
}
打印结果:
&currStack[31]:0x200000AC
&currTCB:0x20000000
&pCurrTCB:0x20000008
pCurrTCB:0x20000000
内存现场分析
关键指令分析:
ldr r3, =pCurrTCB /* [1] */
ldr r1, [r3] /* [2] */
ldr r0, [r1] /* [3] */
- 将pCurrTCB的地址作为立即数赋值给r3,执行该命令后,r3 = 0x20000008, 而0x20000008地址中的内容是0x20000000
- [r3] 的含义是,间接引用,将r3中的值转换成一个内存地址,然后再将这个内存地址中的值赋值给r1,所以我们可以简单推导:
a. 寄存器r3中的值为0x20000008
b. 将0x20000008当成内存地址。
c. 将内存地址0x20000008中的内容0x20000000 赋值给r1.- 将r1中的值作为地址,取出该地址指向的值给r0
a. 寄存器r1中的值为0x20000000
b. 将0x20000000当成内存地址。
c. 将内存地址0x20000000中的内容0x200000AC 赋值给r0
执行完上述3条命令后,r0中的值就是堆栈currStack的栈顶位置了,uC/OS、FreeRTOS都利用了这一特点来通过TCB标号,计算出任务的堆栈栈顶位置,然后进行保存现场/恢复现场。
小结
- LDR指令有两种,分别为LDR指令和LDR伪指令,其中操作数带 = 的为伪指令,对于伪指令可以简单的认为是将 label的地址赋值给寄存器。
- [Rn] 是汇编指令的一种间接引用方法,类似于C的指针,[Rn]的含义是,将Rn中的值作为一个内存地址,然后将该内存地址中的值取出,赋值给另外一个寄存器。
- 通过LDR指令、LDR伪指令、[Rn] 操作,我们可以很方便的使用汇编指令查找到RTOS中的某个任务的堆栈栈顶地址,然后进行进栈保存现场,或出栈恢复现场。
更多推荐
RTOS系列(13):汇编LDR指令、LDR伪指令、[Rn]寄存器间接引用 详细解析
发布评论