RTOS系列(13):汇编LDR指令、LDR伪指令、[Rn]寄存器间接引用 详细解析

编程入门 行业动态 更新时间:2024-10-07 00:14:19

RTOS系列(13):汇编LDR<a href=https://www.elefans.com/category/jswz/34/1769074.html style=指令、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] */
  1. 将pCurrTCB的地址作为立即数赋值给r3,执行该命令后,r3 = 0x20000008, 而0x20000008地址中的内容是0x20000000
  2. [r3] 的含义是,间接引用,将r3中的值转换成一个内存地址,然后再将这个内存地址中的值赋值给r1,所以我们可以简单推导:
    a. 寄存器r3中的值为0x20000008
    b. 将0x20000008当成内存地址。
    c. 将内存地址0x20000008中的内容0x20000000 赋值给r1.
  3. 将r1中的值作为地址,取出该地址指向的值给r0
    a. 寄存器r1中的值为0x20000000
    b. 将0x20000000当成内存地址。
    c. 将内存地址0x20000000中的内容0x200000AC 赋值给r0

执行完上述3条命令后,r0中的值就是堆栈currStack的栈顶位置了,uC/OS、FreeRTOS都利用了这一特点来通过TCB标号,计算出任务的堆栈栈顶位置,然后进行保存现场/恢复现场。

小结

  1. LDR指令有两种,分别为LDR指令和LDR伪指令,其中操作数带 = 的为伪指令,对于伪指令可以简单的认为是将 label的地址赋值给寄存器。
  2. [Rn] 是汇编指令的一种间接引用方法,类似于C的指针,[Rn]的含义是,将Rn中的值作为一个内存地址,然后将该内存地址中的值取出,赋值给另外一个寄存器。
  3. 通过LDR指令、LDR伪指令、[Rn] 操作,我们可以很方便的使用汇编指令查找到RTOS中的某个任务的堆栈栈顶地址,然后进行进栈保存现场,或出栈恢复现场。

更多推荐

RTOS系列(13):汇编LDR指令、LDR伪指令、[Rn]寄存器间接引用 详细解析

本文发布于:2024-02-28 03:20:49,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1768029.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:指令   寄存器   系列   详细   RTOS

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!