ARM64体系结构编程1-加载与存储指令

基本概念

ARM 体系结构是一种硬件规范,主要用来约定指令集,为了降低客户基于 ARM 体系结构开发处理器的难度,ARM 根据不同的应用开发需求开发出箭筒体系结构的处理器 IP,然后授权给客户,比如 ARMv8 体系结构开发出的处理器 IP 有:Cortex-A53, Cortex-A55, Cortex-A72, Cortex-A73,.

ARMv8 体系结构特性

  • 提供超过 4G 物理内存空间
  • 具有 64 位宽的虚拟地址空间,32 位宽的虚拟地址空间只能供 4GB 大小的虚拟空间访问,限制了桌面操作系统和服务器的应用。

ARMv8 体系结构包含多少个通用寄存器?

  • 提供 31 个 64 位宽的通用寄存器。分别是 X0~X30 。可以减少对栈的访问,从而提高性能。
  • 提供 16KB 和 64KB 的页面,有助于降低 TLB 的未命中率。
  • 具有全新的异常处理模型,降低操作系统和虚拟化的实现复杂度。

A64 指令集和 A32 指令集是不兼容的,它们是完全不一样的指令集,它们的指令编码时不一样的,另外 A64 指令集的指令宽度是 32 位,而不是 64 位。

ARMv8 处理器支持两种执行状态, AArch64 和 AArch32 ,当处理器在 AArch64 状态时,运行 A64 指令集,而处理器在 AArch32 状态时,运行 A32 和 T32 指令集,

AArch64 执行状态包含多少个异常等级?分页有什么作用?

AArch64 执行状态的异常等级确定了处理器当前运行的特权级别,类似特权等级。

  • EL0: 用户特权,用于运行普通用户程序
  • EL1: 系统特权,通常用于操作系统内核,如果系统使能了虚拟化扩展,运行虚拟机操作系统内核。
  • EL2: 运行虚拟化扩展的虚拟机监控器
  • EL3: 运行安全世界中的安全监控器

ARMv8 支持的数据宽度

  • 字节(byte): 8位
  • 半字(halfword): 16位
  • 字(word):32位
  • 双字(doubleword): 64位
  • 四字(quadword): 128位

ARMv8 寄存器

31个通用寄存器

在 AArch64 状态下,使用 X(X0、X30) 表示 64 位通用寄存器,另外可以使用 W 来表示低 32 位的数据,如 W0 表示 X0 寄存器的低 32 位数据, W1 表示 X1 寄存器的低 32 位数据。

处理器的状态

AArch64 体系结构使用 PSTATE 寄存器来保存当前处理器状态

条件标志位: N(负位) Z(零位) C(进位) V(溢出标志位)

特殊寄存器

  • 零寄存器

ARMv8 有两个零寄存器,这些寄存器的内容全是 0 ,可以用作源寄存器,也可以用作目标寄存器。WZR 是32位的零寄存器,XZR 是64位的零寄存器。

  • PC 指针寄存器

通常用来指向当前运行指令的下一条指令的地址,用于控制程序的运行顺序。

  • SP 寄存器

ARMv8 支持 4 个异常等级,每个异常等级都有一个 SP 寄存器

- SP_EL0: EL0 下的 SP 寄存器(最低等级)
- SP_EL1: EL1 下的 SP 寄存器
- SP_EL2: EL2 下的 SP 寄存器
- SP_EL3: EL3 下的 SP 寄存器

当处理器运行在 EL0 时,它只能访问 SP_EL0 ,不能访问其他高级的 SP 寄存器。

  • 备份程序状态寄存器

当我们运行一个异常处理程序时,处理器的备份程序会保存到备份程序状态寄存器(SPSR)里面,当异常要发生时,处理器会把 PSTATE 寄存器的值暂时保存到 SPSR 里,当异常处理完成返回时,再把 SPSR 寄存器的值恢复到 PSTATE 寄存器。

  • ELR

存放异常返回地址

  • CurrentEL 寄存器

表示 PSTATE 寄存器中的 EL 字段(当前异常等级),该寄存器保存了当前的异常等级,可以使用 MRS 指令读取当前异常等级。

  • DAIF 寄存器

表示 PSTATE 寄存器中的{D, A, I, F}字段(异常掩码标志位)。

  • SPSel 寄存器

表示 PSTATE 寄存器中的 SP 字段,用于选择某个 SP_ELn 寄存器。

  • PAN 寄存器

表示 PSTATE 寄存器中 PAN(特权禁止访问)字段。可以通过 MSR 和 MRS 指令来设置 PAN 寄存器,主要是为了防止内核态恶意访问用户态内存而新增的,所以需要调用内核提供的结构,比如 copy_from_user() 或者 copy_to_user() 函数。

  • 0: 表示在内核态可以访问用户态内存
  • 1: 表示在内核态访问用户态内存会触发一个访问权限异常
  • UAO 寄存器

表示 PSTAYE 寄存器中的 UAO (用户访问覆盖) 字段。同样可以使用 MSR 和 MRS 指令来设置,

UAO=1 时,表示在 EL1 和 EL2 执行非特权指令(例如 LDTR 和 STTR)的效果和特权指令(例如 LDR、STR)是一样的。

  • NACV 寄存器

表示 PSTATE 寄存器中的{N, Z, C, V}字段 (条件标志位)。

系统寄存器

系统寄存器支持不同的异常等级的访问,通常系统寄存器会使用“Reg_ELn"的方式来表示

  • Reg_EL1: 处理器处于 EL1、EL2 和 EL3 时可以访问该寄存器
  • Reg_EL2: 处理器处于 EL2 和 EL3 时可以访问该寄存器

除了 CTR_EL0 ,大部分系统寄存器不支持处理器处于 EL0 时范访问。


加载与存储指令

在 ARMv8 体系结构下,所以的数据都需要在通用寄存器中完成,而不能直接在内存中完成,因此,首先把待处理的数据从内存加载到通用寄存器,再进行数据处理,最后再把结果写入到内存中。

  • LDR 内存加载指令
  • STR 存储指令
LDR 目标基础漆,<存储器地址>   //把存储器地址中的数据加载到目标寄存器中
STR 源寄存器, <存储器地址>    //把源寄存器的数据存储到存储器中

基于基地址的寻址模式

基地址模式

  • 使用寄存器的值来表示一个地址
  • 把这个内存地址的内容加载到通用寄存器中
LDR Xt, [Xn]  //把 Xn 寄存器中的内容作为内存地址,并把这个内存地址的内容加载到 Xt 寄存器中

STR xt, [Xn]  //把 Xt 寄存器中的内容存储到 Xn 寄存器的内存地址中

基地址+偏移地址模式

  • 基地址 + 偏移地址 = 内存地址
  • 把这个内存地址的值加载到通用寄存器中
LDR Xt, [Xn. #offset] //Xn寄存器中的内容加一个偏移量(offset 必须是 8 的倍数),以相加的结果作为内存地址,加载这个内存地址的内容到 Xt 寄存器中。

STR Xt,[Xn. #offset]  //把 Xt 寄存器的值存储到以 Xn 寄存器的值加一个偏移量表示的内存地址中

基地址扩展模式

LDR <Xt>, [<Xn>, (<Xm>) {, <extend> {<amount>}}]

STR <Xt>, [<Xn>, (<Xm>) {, <extend> {<amount>}}]
  • Xt: 目标寄存器
  • Xn:基地址寄存器
  • Xm,偏移的寄存器
  • extend:扩展/移位 指示符,默认是 LSL(逻辑左移),UXTW(从寄存器中提取32位,其余高位填充0),SXTW(从寄存器中提取32位,其余高位须有符号扩展),SXTX(从寄存器中提取64位数据)
  • amount:索引偏移量,当 extend 不是 LSL 时有效
LDR X0, [X1, X2 LSL #3]   //内存地址为 X1 寄存器的值(X2 寄存器的值<<3),加载这个内存地址的值到 X0 寄存器

LDR X0, [X1, W2, SXTW, #3]  //先对 W2 的值做有效符号扩展,然后左移 3 位,和 X1 寄存器的值相加后得到内存地址,加载这个内存地址的值到 X0 寄存器
变基模式
  • 前变基模式:先更新偏移量地址,后访问内存地址
  • 后变基模式:先访问内存地址,后更新偏移量地址
前变基模式

内存加载指令

LDR Xt, [<Xn|SP>, #<simm>]
  • 首先,Xn/SP 寄存器的值 = Xn/SP 寄存器的值 + simm
  • 以新的 Xn/SP 寄存器的值作为内存地址,并加载这个内存地址的值到 Xt 寄存器

存储指令

STR Xt, [<Xn|SP>, #<simm>]
  • 首先,Xn/SP 寄存器的值 = Xn/SP 寄存器的值 + simm
  • 以新的 Xn/SP 寄存器的值作为内存地址,然后把 Xt 寄存器的值存储到这个内存单元中
后变基模式

内存加载指令

LDR Xt, [<Xn|SP>, #<simm>]
  • 首先,加载 Xn/SP 寄存器的值到 Xt 寄存器
  • 然后更新,Xn/SP 寄存器的值 = Xn/SP 寄存器的值 + simm

存储指令

STR Xt, [<Xn|SP>, $<simm>]
  • 首先,将 Xt 寄存器的值加载到 Xn/SP 寄存器的值为内存地址的内存单元中
  • 然后更新,Xn/SP 寄存器的值 = Xn/SP 寄存器的值 + simm
易混例子
//(X1的值不变)
LDR X0, [X1, #8]    //内存地址为 X1的值+8,加载此内存地址的值到 X0 寄存器

//(X1的值改变)
LDR X0, [X1, #8]!    //前变基模式,先更新 X1 寄存器的值 = X1 寄存器的值 + 8,然后将 X0 的值加载到新的 X1 寄存器值对应的内存单元中

//(X1的值改变)
LDR X0, [X1], #8     //后变基模式,以 X1 的值作为内存地址,加载该内存地址的值到 X0 中,然后更新 X1 寄存器的值 = X1 寄存器的值 + 8

STP X0, X1, [SP, #-16]!   //把 X0 和 X1 寄存器的值压回栈中

LDP X0, X1, [SP], #16	  //把 X0 和 X1 寄存器的值抬出栈
PC相对地址模式

可以使用 lable 标签来标记代码片段,LDR 指令可以访问标签的地址

LDR Xt <label>

读取 label 所在内存地址的内存到 Xt 寄存器中,但是这个 label 必须在当前 PC地址后 1MB 的范围内,否则会报错。

my_data:
	.word 0x40
ldr x0, my_data  // 最终 X0 寄存器的值为 0x40

假设当前 PC 的地址是 0x806E4, 那么这条 LDR 指令读取 0x806E4 + 0x20 地址的内容到 X6 寄存器中。

#define MY_LABEL 0x20
ldr x6, MY_LABEL


// error 0x100000 的偏移量超超出 1MB 范围
#define MY_LABEL_1 0x100000
ldr x6, MY_LABEL_1
LDR 伪指令

伪指令是对汇编器发出的指令,伪指令可以分解为几条指令的集合。

LDR 指令既可以在大范围内加载地址的伪指令,也可以是普通的内存访问指令。当第二个参数前面有 “=” 时,表示伪指令,否则表示普通的内存访问指令。

LDR Xt, =<label>  //把label标记的地址加载到 Xt 寄存器

#define MY_LABEL 0x20
ldr x6, =MY_LABEL
//这里的 ldr 是一条伪指令,它会把 MY_LABEL 宏的值加载到 X6 寄存器中

my_data1:
	.quad 0x8
ldr x5, =my_data1
ldr x6, [x5]
//my_data1 定义了一个数据为 0x8,第一条 LDR 是伪指令,它把 my_data1 对应的地址加载到 X5 寄存器中
//第二条 LDR 是普通的内存访问指令,以 X5 寄存器的值为内存地址,加载这个地址的内容到 X6 中

简而言之,伪指令是把地址给目标寄存器,而普通指令是把地址的值给目标指令

linux 内核的 head.S 中,启动 MMU 之后就使用这个特性实现从运行地址定位到链接地址。

secondary_startup:   
    bl  __cpu_secondary_check52bitva
    bl  __cpu_setup  // initialise processor
    bl  __enable_mmu  
    ldr x8, =__secondary_switched  //跳转到__secondary_switched函数,该函数的地址是链接地址(内核空间的虚拟地址),
	// 在这之前CPU运行在实际物理地址上,实现了地址重定位功能
    br  x8  
ENDPROC(secondary_startup)
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值