锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

ARMv7和ARMv8中关于内存访问的汇编指令总结对比

时间:2023-07-29 10:07:21 2sb1293晶体管晶体管rnrn2412晶体管

ARMv7和ARMv8中关于内存访问的汇编指令总结对比

文章目录

  • ARMv7和ARMv8中关于内存访问的汇编指令总结对比
  • 前言
  • ARMv7下内存访问指令
    • 寻址模式
    • 多加载/存储
  • ARMv8下内存访问指令
    • 加载和存储指令格式
    • 浮点和 NEON 加载和储存标量
    • 指定加载或存储指令的地址
      • 偏移模式
      • 索引模式
    • 访问多个内存位置

前言

ARM计算机精简指令集处理器 (Reduced Instruction Set Computer,RISC) 复杂的指令集计算机(Complex Instruction Set Computer,CISC)例如,处理器X86.指令集丰富,可以用单个指令执行复杂操作。将机器指令解码为内部操作序列(微码,microcode )。相比之下,RISC架构具有较少数量的更通用的指令,这些指令可使用更少的晶体管执行,从而节省生产成本并且更节能。与其他RISC架构一样,ARM内核有大量的通用寄存器,单个周期有许多指令(cycle)内执行。它有一个简单的搜索模式,可以从寄存器内容和指令字段中确定所有加载/存储的地址。

ARM 指令集通常被认为是简单、合乎逻辑和高效的。然而,它不能直接在内存上执行数据处理操作:例如,为了在内存位置增加值,必须首先加载值 ARM 寄存器在寄存器中增加,需要第三条指令将更新值写回内存。

与 x86(但与 68K 不同)一样,ARM 指令通常采用两个或三个操作数格式。在大多数情况下,第一个操作数指定结果的目标(多加载/多存储例外)。相比之下,68K 以目标为最后一个操作数。 ARM 该指令通常对哪些寄存器可用作操作数量没有限制。以下是不同汇编语言的风格:

#将寄存器值自增100指令 x86: add eax, #100 68K: ADD #100, D0 ARM: add r0, r0, 100 #将r保存在1中的地址上的值加载到r0,r11可视为寄存器指针 x86: mov eax, DWORD PTR [ebx] 68K: MOVE.L (A0), D0 ARM: ldr r0, [r1] 

ARMv7和ARMv8都是 加载/存储 这意味着它们不能直接处理内存中的数据, 只有加载和存储指令才能访问内存。因此,通用寄存器需要通过(GPR),将内存中的数据加载到寄存器中,然后存储回内存中。此外,ARMv8具有A64模式和A32模式,即64bits模式和32bits模式,32bits指令集和模式ARMv7指令集几乎一致。所以在学习ARMv同时,7指令集也相当于学习ARMv8的A32模式指令集。

作者最近在做ARMv8下内存复制实验需要进行A64模式和A在32模式下进行,所以对ARMv7和ARMv总结和比较8下内存访问汇编指令。

文章开始前有必要ARMv7和ARMv简要说明寄存器的位宽。一般情况下,ARMv8中用X表示A64下的64bits寄存器,W则是32bits另外还有寄存器 B、H、S、D 以及 Q 寄存器的位宽如下图所示。ARMv7中通用寄存器用R表示ARMv8下W是一致的。

image-20220504144618085

ARMv7下内存访问指令

ARM 内核只在寄存器上执行算术逻辑单元 (ALU) 操作。唯一支持的内存操作是加载(LOAD,LDR,将数据从内存读取到寄存器或存储(STORE,STR,将数据从寄存器写入内存)。

用户可以在指令中添加它 B 表示 Byte(8位)、H 表示Halfword ,半字(16位),或 D表示doubleword ,双字(64 位),指定加载或储存传输的大小,如 LDRB。仅对于加载LDR,还可以使用额外的 S 指示符号字节或半字符(SB 表示符号字节,SH 表示符号半字)。附加类型可以是:

  • B – unsigned Byte. (Zero extend to 32 bits on loads.)
  • SB – signed Byte. (Sign extend to 32 bits.)
  • H – unsigned Halfword. (Zero extend to 32 bits on loads.)
  • SH – signed Halfword. (Sign extend to 32 bits.)

这种方法可能很有用,因为如果是这样的话 8 位或 16 位加载到 32 在寄存器中,必须决定如何处理寄存器中的符号位。无符号数字为零扩展(即寄存器最重要的16或24位设置为零),但对于符号数字,必须将符号位(字节位[7]或半字位[15])复制到寄存器的前16位(或24位)。

寻址模式

LDR和STR有多种寻址模式,如下示例:

  • (1) LDR R0, [R1] ;保存加载地址R1当中
  • (2) LDR R0, [R1, R2] ;加载地址为R1 R2
  • (3) LDR R0, [R1, R2, LSL #2] ;加载的地址R1 (R2*4)逻辑左移1位相当于乘以2
  • (4) LDR R0, [R1, #32]! ;先更新 R1为R1 然后从地址开始R1(R1:=R1 32)上加载
  • (5) LDR R0, [R1], #32 ;先从R加载1个,然后更新 R1为R1 32

方法(1)寄存器寻址(Register addressing)方式,从R1中保存的数据是地址,地址上的数据被加载到R0。

方法(2)和(3)(Pre-indexed addressing),基址寄存器的偏移量在内存访问前添加,基本形式为:LDR Rd, [Rn, Op2] ,偏移可以是正数或负数,也可以是立即数或另一个应用可选移位的寄存器。

方法(4)前下标寻址 写回(Pre-indexed with write-back),它的指令最后使用了一个感叹号!,可理解为优先R1:=R1 32操作,即先更新 R1为R1 32,然后再将R加载1所指向的数据R0当中。

方法(5)后下标 写回(Post-index with write-back),偏移量超过方括号,表示先加载数据,即先从R加载1个,然后更新 R1为R1 32。

多加载/存储

多加载和储存(LDM/STM)使连续的字(word,4字节)可以从存储器中读取或写入。这些对堆栈操作和内存复制非常有用。word可以这样操作,必须使用和使用word对齐地址。以下说明:

LDMIA R10!, { R0-R3, R12 }   

操作数是基址寄存器(可选) !表示基址寄存器的回写,即更新基址寄存器),大括号之间有一个寄存器列表。寄存器列表用逗号分隔,连接字符-用于指示范围。寄存器的加载或存储顺序与列表中指定的顺序无关。相反,操作是固定的,最低编号的寄存器总是映射到最低地址

大括号中有上述示例指令R0,R1,R2,R3和R基址寄存器为125个目标寄存器R指令如下:

[R10] -> R0 [R10 4] -> R1 [R10 8] -> R2 [R10 12] -> R3 [R10 16] -> R12 

由于加了!,操作完成后需要正确R10进行更新,将R10更新为R10 20。

该指令还必须指定如何从基址寄存器中指定 Rd 中继续操作。有四种情况:

  • IA/IB(之后/之前增加,Increment After/Before )
  • DA/DB(之后/之前递减,Decrement After/Before )

后缀也可以用(FD,FA,ED和EA),后缀是从栈stack从角度工作,ARM7支持四种堆栈模式:满递减(FD)、满递增(FA)、空递减(ED)、空递增(EA),并指定栈指针是指向栈的完整性(Full)顶部还是空(Empty)的顶部,而且栈在内存中上升(Ascend)还是下降(Descend)。

  • FD:堆栈地址从上到下递减,指针指向最后一个堆栈元素。
  • FA:堆栈地址自下而上增加,指针指向最后一个堆栈元素。
  • ED:栈地址从上往下递减,且指针指向下一个可用空位。
  • EA:堆栈地址从下网上递增,且指针指向下一个可用空位。

按照惯例,只有(FD) 选项是用于基于 ARM 处理器系统中的栈。这意味着栈指针指向栈内存中最后填充的位置,并且将随着压入到栈的每个新数据项而递减。

STMFD sp!, {r0-r5} ; 将r0至r5压入FD栈,并且更新指针
LDMFD sp!, {r0-r5} ; 将r0至r5弹出FD栈,并且更新指针

下图展示了将R1和R2压入一个FD栈中,在执行 STMFD (PUSH) 指令之前,栈指针SP指向栈中最后一个占用的word。指令完成后,栈指针SP递减 8(两个word),两个寄存器的内容已写入内存,由于内存地址是向上递增,并且按照编号最低的寄存器将写入最低的内存地址的规则,所以R2在R1的上方。

ARMv8下的内存访问指令

与所有以前的 ARM 处理器一样,ARMv8 架构是加载/存储架构。程序必须指定地址、要传输的数据的大小以及源或目标寄存器。ARMv8还有其他加载和存储指令,提供了更多选项,例如非临时加载/存储(non-temporal Load/Store )、加载/存储独占项(Load/Store exclusives),和获取/释放(Acquire/Release)。

加载和存储指令格式

ARMv8中的加载和存储指令和ARMv7中的相同:

LDR Rt,   
STR Rn,   

当加载整数寄存器时,可选择数据大小进行加载,例如,要加载小于指定寄存器值的大小,可将以下后缀之一追加到 LDR 指令中:

  • LDRB (8-bit, zero extended).
  • LDRSB (8-bit, sign extended).
  • LDRH (16-bit, zero extended).
  • LDRSH (16-bit, sign extended).
  • LDRSW (32-bit, sign extended).

还有未缩放的偏移形式,例如LDUR,程序员通常不需要显式使用 LDUR形式,因为大多数汇编程序可以根据使用的偏移量选择适当的版本。

用户不需要指定对 X 寄存器的零扩展加载,因为写入 W 寄存器实际上为零会扩展到整个寄存器宽度。

下图为指令LDRSB W4, 的示意图,在ARMv8中寄存器为64位,W寄存器为A32模式下的描述,所以只用上了前32位。SB为有符号8位扩展,在addr地址上的数据为0x8A,将其加载到W4中,并进行有符号扩展,最后得到的结果为:

下图为指令LDRSB X4, 的示意图,在ARMv8中寄存器为64位,X寄存器为A64模式下的描述,所以为64位。SB为有符号8位扩展,在addr地址上的数据为0x8A,将其加载到X4中,并进行有符号扩展,最后得到的结果为:

下图为指令LDRB W4, 的示意图,SB为零扩展,在addr地址上的数据为0x8A,将其加载到W4中,并进行零扩展,最后得到的结果为:

此外,如果要存储的数据大小可能小于寄存器,可以通过向 STR 添加 B 或 H 后缀来指定此项。

浮点和 NEON 标量加载和存储

加载和存储指令还可以访问浮点/NEON 寄存器。大小仅由正在加载或存储的寄存器确定,寄存器可以是任何 B、H、S、D 或 Q 寄存器。并且浮点和标量 NEON 的加载和存储,使用与整数寄存器加载和存储相同的寻址模式。

需要注意的是,符号扩展(sign-extension)并不支持加载到 FP/SIMD 寄存器中。此类加载的地址仍使用通用寄存器指定。比如:

LDR D0, [X0, X1]  

D寄存器也为64位(doubleword ,8字节),上述指令会将地址X0+X1上的数据加载到D0寄存器当中。

指定加载或存储指令的地址

A64 可用的寻址模式与 A32 和 T32 中的寻址模式类似。有一些额外的限制以及一些新功能,但对于熟悉A32或T32的人来说,A64可用的寻址模式并不陌生。 在 A64 中,地址操作数的基本寄存器必须始终是 X 寄存器。但是,有几条指令支持零扩展或符号扩展,因此可以提供 32 位偏移量作为 W 寄存器。

偏移模式

偏移寻址模式将立即数或可选的修改的寄存器值添加到 64 位基址寄存器以生成地址。

LDR X0, [X1]                ;Load from the address in X1
LDR X0, [X1, #8]            ;Load from address X1 + 8
LDR X0, [X1, X2]            ;Load from address X1 + X2
LDR X0, [X1, X2, LSL, #3]   ;Load from address X1 + (X2 << 3)
LDR X0, [X1, W2, SXTW]      ;Load from address X1 + sign_extend(W2)
LDR X0, [X1, W2, SXTW, #3]  ;Load from address X1 + (sign_extend(W2) << 3)  

通常,在指定移位或扩展选项时,移位量可以是访问大小的 0(默认值)或 log2(以字节为单位)(以便 Rn << 将 Rn 乘以访问大小)。

以下是一段C程序的示例:

// A C example showing accesses that a compiler is likely to generate.
void example_dup(int32_t a[], int32_t length) 
{ 
        
	int32_t first = a[0]; // LDR W3, [X0]
	for (int32_t i = 1; i < length; i++) 
    { 
        
		a[i] = first; // STR W3, [X0, W2, SXTW, #2]
	}
}

索引模式

索引模式类似于偏移模式,但它们会更新基址寄存器。语法与 A32 和 T32 中的语法相同,但操作集的限制性更强。通常,只能为索引模式提供立即数偏移量。同ARMv7一样,有两种变体:前索引模式(在访问内存之前应用偏移量)和后索引模式(在访问内存后应用偏移量):

LDR X0, [X1, #8]!         ;Pre-index: Update X1 first (to X1 + #8), then load from the new address
LDR X0, [X1], #8          ;Post-index: Load from the unmodified address in X1 first, then update X1 (to X1 + #8)
STP X0, X1, [SP, #-16]!   ;Push X0 and X1 to the stack.
LDP X0, X1, [SP], #16     ;Pop X0 and X1 off the stack  

C语言示例:

// A C example showing accesses that a compiler is likely to generate.
void example_strcpy(char * dst, const char * src)
{ 
        
	char c;
do { 
        
	c = *(src++); // LDRB W2, [X1], #1
	*(dst++) = c; // STRB W2, [X0], #1
	} while (c != '\0');
}

访问多个内存位置

A64 不包括 A32 和 T32 代码可用的多加载 (LDM) 或多存储(STM) 指令

在 A64 代码中,有成对加载(Load Pair ,LDP) 和存储对(Store Pair,STP)。与 A32中的LDRD 和 STRD 指令不同,LDP/STP可以读取或写入任何两个整数寄存器。数据被读取或写入到相邻的内存位置或从相邻的内存位置写入。为这些指令提供的寻址模式选项比其他内存访问指令更具限制性。

LDP 和 STP 指令只能使用具有缩放的 7 位有符号立即数的基址寄存器,并具有可选的预增量或后增量。与 32 位 LDRD 和 STRD 不同,LDP 和 STP 可以进行未对齐的访问。

  • 指令LDP W3, W7, [X0],它将[X0]上的数据加载到W3,将[X0+4]加载到W7,如图:

  • 指令LDP X8, X2, [X0, #0x10]! ,将[X0, #0x10]上的数据加载到X8,将[X0 + 0x10 + 8]上的数据加载到X2,如图:

  • 指令LDPSW X3, X4, [X0],将[X0]加载到X3,将[X0+4]加载到X4,并且进行有符号扩展,扩展到64bits

  • 指令LDP D8, D2, [X11], #0x10 ,将[X11]加载到D8,将[X11+8]加载到D2,最后将X11更新为X11+0x10

  • 指令STP X9, X8, [X4], 将64bits的数据从X9 存储到地址[X4],并且将X8中的64bits数据存储到[X4+8]

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章