···························································· 第3 章 chapter3 ARMv8架构基础知识 ARMv8架构是ARM 体系结构的一种,它具有引人注目的特点:首次引入了64位 指令集,从而能够处理更大的内存空间和更复杂的数据计算。此外,ARMv8架构还引入 了全新的执行模式AArch64,它能够运行64位指令集,支持更广泛的操作模式和高级特 性。目前,基于ARMv8架构设计的处理器在各个领域都发挥着重要的作用。 本章将从ARMv8架构的基础概念、寄存器组、A64指令集、ARM64异常处理以及 ARM64内存管理等方面介绍ARMv8架构,旨在帮助读者更好地理解ARMv8架构。 3.1 ARMv8 架构 ARMv8是ARM 公司推出的处理器架构,广泛应用于移动设备、嵌入式系统、物联 网设备和云服务器等领域,了解ARMv8架构可以理解和应用这些设备和系统。许多应 用和系统都是基于ARMv8架构开发的,学习ARMv8架构可以更好地理解和开发这些 应用和系统。同时,了解ARMv8架构也可以提高编程技能和工作竞争力。本节主要对 ARMv8中涉及的主要概念进行梳理,介绍基于ARMv8架构设计的处理器的运行状态 和其支持的数据宽度。 3.1.1 ARMv8 架构介绍 1.ARMv8-A 架构特性 ARMv8-A 是ARM 公司发布的第一代支持64位处理器的指令集和架构,它在扩充 64位寄存器的同时提供了对上一代架构指令集的兼容,因此它提供了运行32位和64位 应用程序的环境。 ARMv8-A 架构除了提高了处理能力,还引入了很多吸引人的新特性。 . 具有超大物理地址空间,提供超过4GB物理内存。 . 具有64位宽的虚拟地址空间。32位宽的虚拟地址空间只能提供4GB大小的虚 拟地址空间访问,这极大地限制了桌面操作系统和服务器等的性能发挥。64位 宽的虚拟地址空间可以提供更大的访问空间。 . 提供31个64位宽的通用寄存器,可以减少对栈的访问,从而提高性能。 5 0 ◆嵌入式系统开发与应用 . 提供16KB和64KB的页面,有助于降低TLB的未命中率(missrate)。 . 具有全新的异常处理模型,有助于降低操作系统和虚拟化的实现复杂度。 . 具有全新的加载-获取指令(load-acquireinstruction)、存储-释放指令(storereleaseinstruction), 专门为C++11、C11以及Java内存模型设计。 2.采用ARMv8架构的常见处理器内核 下面介绍市面上常见的采用ARMv8架构的处理器(简称ARMv8处理器)内核。 (1)Cortex-A53处理器内核。ARM 公司第一款采用ARMv8-A 架构的处理器内 核,专门为低功耗设计的处理器。通常可以使用1~4个Cortex-A53处理器组成一个处 理器簇,或者和Cortex-A57或Cortex-A72等高性能处理器组成大/小核架构。 (2)Cortex-A57处理器内核。采用64位ARMv8-A 架构的处理器内核,而且通过 AArch32执行状态,保持与ARMv7架构完全后向兼容。除了ARMv8架构的优势之外, Cortex-A57还提高了单个时钟周期的性能,比高性能的Cortex-A15高出20%~40%; 它还改进了二级高速缓存的设计和内存系统的其他组件,极大地提高了性能。 (3)Cortex-A72处理器内核。2015年年初正式发布的基于ARMv8-A 架构,并在 Cortex-A57处理器上做了大量优化和改进。在相同的移动设备电池寿命限制下, Cortex-A72相较于基于Cortex-A15的设备具有3.5倍的性能提升,展现出了优异的整 体功耗效率。 3.1.2 ARMv8 基础概念 ARM 处理器实现的是精简指令集架构。在ARMv8-A架构中有以下基本概念和定义。 (1)处理机(processingelement,PE)。在ARM 公司的官方技术手册中提到的一个 概念,把处理器处理事务的过程抽象为处理机。 (2)执行状态(executionstate)。处理器运行时的环境,包括寄存器的位宽、支持的 指令集、异常模型、内存管理以及编程模型等。ARMv8架构定义了两个执行状态。 ① AArch64:64位的执行状态。 . 提供31个64位的通用寄存器。 . 提供64 位的程序计数器(program counter,PC)指针寄存器、栈指针(stack pointer,SP)寄存器以及异常链接寄存器(exceptionlinkregister,ELR)。 . 提供A64指令集。 . 定义ARMv8异常模型,支持4个异常等级,即EL0~EL3。 . 提供64位的内存模型。 . 定义一组处理器状态(PSTATE)用来保存PE的状态。 ② AArch32:32位的执行状态。 . 提供13个32位的通用寄存器,再加上PC指针寄存器、SP寄存器、链接寄存器 (linkregister,LR)。 . 支持两套指令集,分别是A32和T32指令集(Thumb指令集)。 . 支持ARMv7-A 异常模型,基于PE模式并映射到ARMv8的异常模型中。 第◆3 章 ARMv8 架构基础知识5 1 . 提供32位的虚拟内存访问机制。 . 定义一组PSTATE用来保存PE的状态。 (3)ARMv8指令集。ARMv8架构根据不同的执行状态提供不同指令集的支持。 . A64指令集:运行在AArch64状态下,提供64位指令集支持。 . A32指令集:运行在AArch32状态下,提供32位指令集支持。 . T32指令集:运行在AArch32状态下,提供16和32位指令集支持。 (4)系统寄存器命名。在AArch64状态下,很多系统寄存器会根据不同的异常等级 提供不同的变种寄存器。 3.1.3 ARMv8 处理器的运行状态 ARMv8处理器支持两种执行状态———AArch64状态和AArch32状态。AArch64 状态是ARMv8新增的64位执行状态,而AArch32是为了兼容ARMv7架构的32位执 行状态。当处理器运行在AArch64状态下时,运行A64指令集;而当运行在AArch32 状态下时,可以运行A32指令集或者T32指令集。 如图3-1所示,AArch64状态的异常等级(exceptionlevel)决定了处理器当前运行的 特权级别,类似于ARMv7架构中的特权等级。 图3-1 AArch64状态的异常等级 . EL0:用户特权,用于运行普通用户程序。 . EL1:系统特权,通常用于运行操作系统。 . EL2:运行虚拟化扩展的虚拟监控程序(hypervisor)。 . EL3:运行安全世界中的安全监控器(securemonitor)。 ARMv8架构允许切换应用程序的运行模式。例如在一个运行64位操作系统的 ARMv8处理器中,可以同时运行A64指令集的应用程序和A32指令集的应用程序。但 是在一个运行32位操作系统的ARMv8处理器中就不能运行A64指令集的应用程序 了。当需要运行A32指令集的应用程序时,需要通过一条管理员调用(supervisorcall, SVC)指令切换到EL1,操作系统会做任务的切换并返回AArch32的EL0中,这时操作 系统就为这个应用程序准备好了AArch32的运行环境。 3.1.4 ARMv8 架构支持的数据宽度 ARMv8支持如下几种数据宽度。 5 2 ◆嵌入式系统开发与应用 . 字节(byte):8位。 . 半字(halfword):16位。 . 字(word):32位。 . 双字(doubleword):64位。 . 四字(quadword):128位。 不对齐访问有两种情况,一种是指令不对齐访问,另一种是数据不对齐访问。A64 指令集要求指令存放的位置必须以字(word,32位宽)为单位对齐。访问一条存储位置 不以字为单位对齐的指令会导致PC对齐异常(PCalignmentfault)。 对于数据访问,需要区分不同的内存类型。内存类型是设备内存的不对齐访问会触 发一个对齐异常(alignmentfault)。 对于访问普通内存,除了独占加载/独占存储(load-exclusive/store-exclusive)指令或 者加载-获取/存储-释放(load-acquire/store-release)指令外,对于其他加载或者存储单个 或多个寄存器的所有指令,如果访问地址和要访问数据不对齐,则按照以下两种情况进 行处理。 . 若对应的异常等级中的SCTLR_Elx.A 设置为1,则说明打开了地址对齐检查功 能,那么会触发一个对齐异常。 . 若对应的异常等级中的SCTLR_Elx.A 设置为0,那么处理器支持不对齐访问。 当然,处理器对不对齐访问也有一些限制。 . 不能保证单次原子地完成访问,可能多次复制。 . 不对齐访问比对齐访问需要更多的处理时间。 . 不对齐访问可能会造成中止(abort)。 3.2 ARMv8 寄存器 了解ARMv8寄存器的结构、数量和使用方式,对理解指令集的执行、优化代码、调试 程序以及进行性能分析非常有必要。因此本节通过介绍ARMv8架构的主要寄存器:通 用寄存器、处理状态寄存器、特殊寄存器和系统寄存器,帮助读者认识ARMv8寄存器。 3.2.1 通用寄存器 AArch64运行状态支持31 个64 位的通用寄存器,分别是X0~X30 寄存器,而 AArch32状态支持16个32位的通用寄存器。 通用寄存器除了用于数据运算和存储之外,还可以在函数调用过程中起到特殊作 用,ARM64架构的函数调用标准和规范对此有所约定,如图3-2所示。 在AArch64状态下,使用X表示64位通用寄存器,如X0、X30等。另外,还可以使 用W 表示X寄存器的低32位的数据,如W0表示X0寄存器的低32位数据,W1表示 X1寄存器的低32位数据,如图3-3所示。 第◆3 章 ARMv8 架构基础知识5 3 图3-2 AArch64状态的31个通用寄存器 图3-3 64位通用寄存器和低32位数据 3.2.2 处理器状态寄存器 在ARMv7架构中,使用当前程序状态寄存器(currentprogramstatusregister, CPSR)表示当前的处理器状态(processorstate),而在AArch64中使用PSTATE寄存器 表示,如表3-1所示。 表3-1 PSTATE寄存器 分 类字 段描 述 条件标志位 N 负数标志位 在结果是有符号的二进制代码的情况下,如果结果为负数,则N=1;如果 结果为非负数,则N=0 Z 0标志位 如果结果为0,则Z=1;如果结果为非0,则Z=0 C 进位标志位 当发生无符号数溢出时,C=1 其他情况下,C=0 V 有符号数溢出标志位 对于加/减指令,在操作数和结果是有符号的整数时,如果发生溢出,则V =1;如果未发生溢出,则V=0 对于其他指令,V通常不发生变化 续表 5 4 ◆嵌入式系统开发与应用 分 类字 段描 述 运行状态控制 SS 软件单步。该位为1,说明在异常处理中使能了软件单步功能 IL 不合法的异常状态 nRW 当前执行模式 0:处于AArch64状态 1:处于AArch32状态 EL 当前异常等级 0:表示EL0 1:表示EL1 2:表示EL2 3:表示EL3 SP 选择SP寄存器。当运行在EL0时,处理器选择EL0的SP寄存器,即SP_ EL0;当处理器运行在其他异常等级时,处理器可以选择使用SP_EL0或者 对应的SP_ELn寄存器 异常掩码标志位 D 调试位。使能该位可以在异常处理过程中打开调试断点和软件单步等 功能 A 用来屏蔽系统错误(SError) I 用来屏蔽IRQ F 用来屏蔽FIQ 访问权限 PAN 特权不访问(privilegedaccessnever)位是ARMv8.1的扩展特性 1:在EL1或者EL2访问属于EL0的虚拟地址时会触发一个访问权限 错误 0:不支持该功能,需要软件模拟 UAO 用户特权访问覆盖标志位,是ARMv8.2的扩展特性 1:当运行在EL1或者EL2时,没有特权的加载存储指令可以和有特权 的加载存储指令一样访问内存,如LDTR指令 0:不支持该功能 3.2.3 特殊寄存器 ARMv8架构除了支持31个通用寄存器之外,还提供多个特殊寄存器,如图3-4 所示。 1.零寄存器 ARMv8架构提供两个零寄存器(zeroregister),这些寄存器的内容全是0,可以用作 源寄存器,也可以用作目标寄存器。WZR寄存器是32位的零寄存器,XZR是64位的零 寄存器。 ◆ 第 3 章 ARMv8 架构基础知识55 图3- 4 特殊寄存器 2.PC寄存器 PC寄存器通常用来指向当前运行指令的下一条指令的地址,用于控制程序中指令 的运行顺序,但是编程人员不能通过指令直接访问它。 3.SP寄存器 ARMv8架构支持4个异常等级,每一个异常等级都有一个专门的SP寄存器SP_ ELn,例如处理器运行在EL1时选择SP_EL1寄存器作为SP寄存器。 .SPEL0:EL0下的SP寄存器。 .SPL1:EL1下的SP寄存器。E(_) .SPL2:EL2下的SP寄存器。E(_) .SP_L3:EL3下的SP寄存器 。 当处理器运行在比EL0高的异常等级时,处理器可以访问如下寄存器 。E(_) .当前异常等级对应的SP寄存器SP_ELn。 .EL0对应的SP寄存器SP_EL0可以当作一个临时寄存器,如Linux内核里使用 该寄存器存放进程的task_struct数据结构的指针。 当处理器运行在EL0时,它只能访问SP_EL0,而不能访问其他高级的SP寄存器。 4.保存处理状态寄存器 当运行一个异常处理器时,处理器的处理状态会保存到保存处理状态寄存器(saved processtatusregister,SPSR)中,这个寄存器非常类似于ARMv7架构中的CPSR 。当 异常将要发生时,处理器会把PSTATE寄存器的值暂时保存到SPSR中;当异常处理完 成并返回时,再把SPSR的值恢复到PSTATE寄存器。SPSR的格式如图3-5所示, SPSR的重要字段如表3-2所示。 ◆ 56 嵌入式系统开发与应用 图3- 5 SPSR 的格式 表3- 2 SPSR 的重要字段 字段描述 N 负数标志位 Z 零标志位 C 进位标志位 V 有符号数溢出标志位 DIT 与数据无关的指令时序(dataindependenttiming),ARMv8.4的扩展特性 UAO 用户特权访问覆盖标志位,ARMv8.2的扩展特性 PAN 特权模式禁止访问(privilegedacesnever)位,ARMv8.1的扩展特性 SS 表示是否使能软件单步功能。若该位为1,说明在异常处理中使能了软件单步功能 IL 不合法的异常状态 D 调试位。使能该位可以在异常处理过程中打开调试断点和软件单步等功能 A 用来屏蔽系统错误 I 用来屏蔽IRQ F 用来屏蔽FIQ M[4] 用来表示异常处理过程中处于哪个执行状态,若为0,表示AArch64状态 M[3:0] 异常模式 5.ELR 寄存器 该寄存器存放了异常返回地址。 6.CurentEL寄存器 该寄存器表示PSTATE寄存器中的EL字段,其中保存了当前异常等级。使用 MRS指令可以读取当前异常等级。 .0:表示EL0 。 .1:表示EL1 。 .2:表示EL2 。 第◆3 章 ARMv8 架构基础知识5 7 .3:表示EL3。 7.DAIF寄存器 该寄存器表示PSTATE寄存器中的{D,A,I,F}字段。 8.SPSel寄存器 该寄存器表示PSTATE寄存器中的SP字段,用于在SP_EL0和SP_ELn中选择SP 寄存器。 9.PAN 寄存器 该寄存器表示PSTATE寄存器中的PAN(privilegedaccessnever,特权禁止访问) 字段。可以通过MRS和MSR指令设置PAN 寄存器。 10.UAO 寄存器 该寄存器表示PSTATE寄存器中的UAO(useraccessoverride,用户访问覆盖)字 段。可以通过MRS和MSR指令设置UAO 寄存器。 11.NZCV 寄存器 该寄存器表示PSTATE寄存器中的{N,Z,C,V }字段。 3.2.4 系统寄存器 除了上面介绍的通用寄存器和特殊寄存器之外,ARMv8架构还定义了很多的系统 寄存器,通过访问和设置这些系统寄存器可以完成对处理器不同功能的配置。在 ARMv7架构中,需要通过访问CP15协处理器间接访问这些系统寄存器,而在ARMv8 架构中没有协处理器,可直接访问系统寄存器。ARMv8架构支持如下7类系统寄存器。 . 通用系统控制寄存器。 . 调试寄存器。 . 性能监控寄存器。 . 活动监控寄存器。 . 统计扩展寄存器。 . RAS寄存器。 . 通用定时器寄存器。 系统寄存器支持不同异常等级的访问,通常系统寄存器会使用Reg_ELn的方式 表示。. Reg_ELl:处理器处于EL1、EL2以及EL3时可以访问该寄存器。 . Reg_EL2:处理器处于EL2和EL3时可以访问该寄存器。 . 大部分系统寄存器不支持处理器处于EL0时访问,但也有一些例外,如CTR_ EL0寄存器。 5 8 ◆嵌入式系统开发与应用 程序可以通过MRS和MSR指令访问系统寄存器。 MRS X0, TTBR0_EL1 //把TTBR0_EL1 的值复制到x0 寄存器 MSR TTBR0_EL1, X0 //X0 寄存器的值复制到TTBR0_EL1 3.3 A64 指令集 指令集是处理器体系结构设计的重点之一。ARM 公司定义和实现的指令集一直在 变化和发展中。ARMv8体系结构最大的改变是增加了一个新的64位指令集,这是早前 ARM 指令集的有益补充和增加,它可以处理64位宽的寄存器和数据,并且使用64位的 指针访问内存。这个新的指令集称为A64指令集,运行在AArch64状态。ARMv8兼容 旧的32位指令集———A32指令集,它运行在AArch32状态。A64和A32指令集并不兼 容,它们是两套不同的指令集,指令编码是不一样的。需要注意的是,A64指令集支持64 位宽的数据和地址寻址,但其编码宽度是32位,而不是64位。A64指令集具有以下特 点:具有特有的指令编码格式;只能运行在AArch64状态;指令的宽度为32位。 下面以前变基模式的LDR指令为例,介绍A64指令集的编码风格,如图3-6所示。 图3-6 前变基模式的LDR 指令的编码 第0~4位为Rt字段,用来描述目标寄存器Xt,可以从X0~X30中选择。 第5~9位为Rn字段,用来描述基地址寄存器Xn,可以从X0~X30中选择,也可以 选择SP寄存器作为第31个寄存器。 第12~20位为imm9字段,用于偏移量imm。 第21~29位用于指令分类。 第30~31位为size字段,当size为0b11时,表示64位宽数据;当size为0b10时,表 示32位宽数据。 A64指令集可以分为如下几类: . 内存加载和存储指令; . 多字节内存加载和存储指令; . 算术和移位指令; . 移位操作指令; . 位操作指令; . 条件操作指令; . 跳转指令; . 独占访问指令; 第◆3 章 ARMv8 架构基础知识5 9 . 内存屏障指令; . 异常处理指令; . 系统寄存器访问指令。 3.3.1 加载与存储指令 和早期的ARM 体系结构一样,ARMv8体系结构基于指令加载和存储体系结构。 在这种体系结构下,所有的数据处理都需要在通用寄存器中完成,而不能直接在内存中 完成。因此,首先把待处理数据从内存加载到通用寄存器,然后进行数据处理,最后把数 据写入内存。 常见的内存加载指令是LDR 指令,内存写入指令是STR 指令。LDR 指令和STR 指令的基本格式如下。 LDR 目标寄存器, <存储器地址> //把存储器地址中的数据加载到目标寄存器中 STR 源寄存器, <存储器地址> //把源寄存器的数据存储到存储器中 1.基地址模式的寻址 基地址模式首先是使用寄存器的值表示一个地址,然后把这个内存地址的内容加载 到通用寄存器中。 以下指令以Xn寄存器中的内容作为内存地址,加载此内存地址的内容到Xt寄 存器。 LDR Xt, [Xn] 以下指令是把Xt寄存器中的内容存储到Xn寄存器的内存地址中。 STR Xt, [Xn] 2.基地址加偏移量模式的寻址 基地址加偏移量模式是指在基地址的基础上再加上偏移量,从而计算内存地址,并 把这个内存地址的值加载到通用寄存器中,偏移量可以是正数,也可以是负数。 以下指令是把Xn寄存器的内容加上一个偏移量(offset必须是8的倍数),以相加的 结果作为基地址,加载此内存地址的内容到Xt寄存器。 LDR Xt, [Xn, #offset] 基地址加偏移量模式的存储指令格式如下。该指令是将Xt寄存器中的值存储到以 Xn寄存器的值加一个偏移量(offset必须是8的倍数)表示的地址中。 STR Xt, [Xn, #offset] 3.基地址扩展模式的寻址 基地址扩展模式的命令如下。 6 0 ◆嵌入式系统开发与应用 LDR <Xt>, [<Xn>, (<Xm>){,<extend>{<amount>}}] STR <Xt>, [<Xn>, (<Xm>){,<extend>{<amount>}}] Xt:目标寄存器。 Xn:基地址寄存器。 Xm:用来表示偏移的寄存器。 extend:扩展/移位指示符,默认是LSL,也可以是UXTW、SXTW、SXTX。 amount:索引偏移量,amount的值只能是0或者3,如果是其他值,汇编器将报错。 【例3-1】 如下代码使用了基于基地址加偏移量模式。 LDR X0, [X1] //内存地址为X1 寄存器的值,加载此内存地址的值到X0 寄存器 LDR X0, [X1,#8] //内存地址为X1 寄存器的值再加上偏移量(8),加载此内存地址的值到X0 寄存器 LDR X0,[X1,X2] //内存地址为X1 寄存器的值加X2 寄存器的值,加载此内存地址的值到X0 寄存器 LDR X0,[X1,X2,LSL #3] //内存地址为X1 寄存器的值加(X2 寄存器的值< < 3),加载此内存地址的值到X0 寄存器 LDR X0,[X1,W2,SXTW] //先对W2 的值做有符号的扩展,和X1 寄存器的值相加后,将结果作为内存地址, //加载此内存地址的值到X0 寄存器 LDR X0,[X1,W2,SXTW #3] //先对W2 的值做有符号的扩展,然后左移3 位,和X1 寄存器的值相加后,将 //结果作为内存地址,加载此内存地址的值到X0 寄存器 4.前变基模式的寻址 前变基模式指先更新偏移量地址,后访问内存地址。前变基模式的指令格式如下。 首先更新Xn|SP寄存器的值为Xn|SP寄存器的值加imm,然后以新的Xn|SP的值为内 存地址,加载该内存地址的值到Xt寄存器。 LDR <Xt>, [<Xn|SP>, #<imm>]! 以下指令首先更新Xn|SP的值为Xn|SP寄存器的值加imm,然后把Xt寄存器的值 存储到Xn|SP寄存器的新值为地址的内存单元中。 STR <Xt>, [<Xn|SP>, #<imm>]! 5.后变基模式的寻址 后变基模型指先访问内存地址,后更新偏移量地址。首先以Xn|SP寄存器的值为内 存地址,取该内存地址上的值到Xt寄存器,再更新Xn|SP寄存器的值为Xn|SP寄存器 的值加imm。 第◆3 章 ARMv8 架构基础知识6 1 LDR <Xt>, [<Xn|SP>], #<imm> 以下指令先将Xt寄存器的值存储到以Xn|SP寄存器的值为地址的内存单元中,然 后更新Xn|SP寄存器的值为Xn|SP寄存器的值加imm。 STR <Xt>, [<Xn|SP>], #<imm> 【例3-2】 如下代码使用了前变基模式和后变基模式。 LDR X0, [X1, #8]! //前变基模式。先更新X1 寄存器的值为X1 寄存器的值加8 //然后以新的X1 寄存器的值为内存地址,加载该内存地址的值到X0 寄存器中 LDR X0, [X1], #8 //后变基模式。以X1 寄存器的值为内存地址,加载该内存地址的值到X0 寄存器,然后更 //新X1 寄存器的值为X1 寄存器的值加8 6.PC相对地址模式的寻址 汇编代码中常常会使用标签(label)标记代码片段。LDR 指令还提供一种访问标签 的地址模式,指令的格式如下。这条指令驱动label所在内存地址的内容到Xt寄存器 中。但是这个label必须在当前PC地址前后1MB的范围内,如果超过这个范围,则汇编 器会报错。 LDR <Xt>, <label> 【例3-3】 如下LDR指令会把标签my_data的数据读出来。 my_data: .word 0x40 ldr x0, my_data //最终X0 寄存器的值为0x40 【例3-4】 假设当前PC值为0x806E4,那么这条LDR指令读取0x806E4+0x20地 址的内容到X6寄存器中。 #define MY_LABEL 0x20 ldr x6,MY_LABEL 7.多字节内存加载和存储 A32指令集提供LDM 和STM 指令以实现多字节内存加载与存储,而A64指令集 不再提供LDM 和STM 指令,而是提供LDP和STP指令。LDP和STP指令支持3种 寻址模式。 基地址偏移量模式LDP指令的格式如下,它以Xn|SP寄存器的值为基地址,然后读 取Xn|SP寄存器的值加imm 地址的值到Xt1寄器,读取Xn|SP寄存器的值加imm+8 地址的值到Xt2寄存器中。 LDP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}] 6 2 ◆嵌入式系统开发与应用 基地址偏移量模式STP指令的格式如下,它以Xn|SP寄存器的值为基地址,然后把 Xt1寄存器的内容存储到[Xn|SP+imm]处,把Xt2寄存器的内容存储到[Xn|SP+imm +8]处。 STP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}] 前变基模式LDP指令的格式如下,它先计算Xn寄存器的值加imm,并存储到Xn 寄存器中,然后以Xn寄存器的最新值作为基地址,读取Xn寄存器的值加imm 地址的值 到Xt1寄存器,读取[Xn+imm+8]的值到Xt2寄存器中。Xn寄存器可以使用SP寄 存器。 LDP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]! 前变基模式STP指令的格式如下,它先计算Xn寄存器的值加imm,并存储到Xn寄 存器中,然后以Xn寄存器的最新值作为基地址,把Xt1寄存器的内容存储到Xn内存地 址处,把Xt2寄存器的值存储到Xn寄存器的值加8对应的内存地址处。 STP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]! 后变基模式LDP指令的格式如下,它以Xn寄存器的值为基地址,读取[Xn+imm] 的值到Xt1寄存器,读取[Xn+imm+8]的值到Xt2寄存器中,最后更新Xn寄存器。Xn 寄存器可以使用SP寄存器。 LDP <Xt1>, <Xt2>, [<Xn|SP>], #<imm> 后变基模式STP指令格式如下,它以Xn寄存器的值为基地址,把Xt1寄存器的内 容存储到Xn寄存器的值加imm 对应的内存地址处,Xt2寄存器的值存储到[Xn+imm +8]处,并更新Xn寄存器。Xn寄存器可以使用SP寄存器。 STP <Xt1>, <Xt2>, [<Xn|SP>], #<imm> 【例3-5】 如下代码使用了基地址偏移量模式和前变基模式。 LDP X3, X7, [X0] //以X0 寄存器的值为内存地址,加载此内存地址的值到X3 寄存器中,然后以X0 寄存器的 // 值加8 作为内存地址,加载此内存地址的值到X7 寄存器中 LDP X1, X2, [X0, #0x10]! //前变基模式。先计算X0+0x10 作为X0 的值,然后以X0 寄存器的值作为内存地址 //加载此内存地址的值到X1 寄存器中,接着以X0 寄存器的值加8 作为内存地址 //加载此内存地址的值到X2 寄存器中 STP X1, X2, [X4] //存储X1 寄存器的值到地址为X4 寄存器的值的内存单元中 // 然后存储X2 寄存器的值到地址为X4 寄存器的值加8 的内存单元中 8.不同位宽的加载与存储指令 LDR和STR指令根据不同的数据位宽有多种变种,如表3-3所示。 第◆3 章 ARMv8 架构基础知识6 3 表3-3 不同位宽的LDR 和STR 指令 指 令描 述 LDR 数据加载指令 LDRSW 有符号的数据加载指令,单位为字 LDRB 数据加载指令,单位为字节 LDRSB 有符号的数据加载指令,单位为字节 LDRH 数据加载指令,单位为半字 LDRSH 有符号的数据加载指令,单位为半字 STR 数据存储指令 STRB 数据存储指令,单位为字节 STRH 数据存储指令,单位为半字 9.不可扩展的加载和存储指令 LDR指令中的基地址加偏移量模式为可扩展模式,即偏移量按照数据大小来扩展且 是正数,取值范围为0~32760。A64指令集还支持一种不可扩展模式的加载和存储指 令,即偏移量只能按照字节来扩展,可以是正数或者负数,取值范围为-256~255,例如 LDUR指令。因此,可扩展模式和不可扩展模式的区别在于是否按照数据大小进行扩 展,从而扩大寻址范围。 LDUR指令的格式如下。LDUR指令的意思是以Xn|SP寄存器的内容加一个偏移 量(imm)作为内存地址,加载此内存地址的内容(8字节数据)到Xt寄存器。 LDUR <Xt>, [<Xn|SP>{, #<imm>}] 同理,不可扩展模式的存储指令为STUR,其指令格式如下。STUR指令是把Xt寄 存器的内容存储到Xn|SP寄存器加上imm 偏移量的地方。 STUR <Xt>, [<Xn|SP>{, #<imm>}] 不可扩展模式的LDUR和STUR指令根据数据位宽有多种变种,如表3-4所示。 表3-4 不可扩展模式的LDUR 和STUR 指令 指 令描 述 LDUR 数据加载指令 LDURSW 有符号的数据加载指令,单位为字 LDURB 数据加载指令,单位为字节 LDURSB 有符号的数据加载指令,单位为字节 LDURH 数据加载指令,单位为半字 续表 6 4 ◆嵌入式系统开发与应用 指 令描 述 LDURSH 有符号的数据加载指令,单位为半字 STUB 数据存储指令 STURB 数据存储指令,单位为字节 STURH 数据存储指令,单位为半字 10.独占内存访问指令 ARMv8体系结构提供独占内存访问(exclusivememoryaccess)的指令。LDXR 指 令尝试在内存总线中申请一个独占访问的锁,然后访问一个内存地址。STXR指令往刚 才LDXR指令已经申请独占访问的内存地址中写入新内容。LDXR和STXR指令通常 组合使用以完成一些同步操作,如Linux内核的自旋锁。 另外,ARMv7和ARMv8还提供多字节独占内存访问指令,如表3-5所示。 表3-5 独占内存访问指令 指 令描 述 LDXR 独占内存访问指令。指令的格式如下 LDXR Xt,[Xn|SP{,#0}]; STXR 独占内存访问指令。指令的格式如下 STXR Ws,Xt,[Xn|SP{,#0}]; LDXP 多字节独占内存访问指令。指令的格式如下 LDXP Xt1,Xt2,[Xn|SP{,#0}]; STXP 多字节独占内存访问指令。指令的格式如下 STXP Ws,Xt1,Xt2,[Xn|SP{,#0}]; 11.隐含加载-获取/存储-释放内存屏障原语 ARMv8体系结构提供一组新的加载和存储指令,其中包含内存屏障原语,如表3-6所示。 表3-6 隐含屏障原语的加载和存储指令 指令描 述 LDAR 加载-获取(load-acquire)指令。LDAR指令后面的读写内存指令必须在LDAR指令之后执行 STLR 存储-释放(store-release)指令。所有的加载和存储指令必须在STLR指令之前完成 12.非特权访问级别的加载和存储指令 ARMv8体系结构实现了一组非特权访问级别的加载和存储指令,它适用于在EL0 进行的访问,如表3-7所示。 第◆3 章 ARMv8 架构基础知识6 5 表3-7 非特权访问级别的加载和存储指令 指 令描 述 LDTR 非特权加载指令 LDTRB 非特权加载指令,加载1字节 LDTRSB 非特权加载指令,加载有符号的1字节 LDTRH 非特权加载指令,加载2字节 LDTRSH 非特权加载指令,加载有符号的2字节 LDTRSW 非特权加载指令,加载有符号的4字节 STTR 非特权存储指令,存储8字节 STTRB 非特权存储指令,存储1字节 STTRH 非特权存储指令,存储2字节 当PSTATE寄存器中的UAO 字段为1时,在EL1和EL2执行这些非特权指令的 效果和执行特权指令是一样的,这个特性是在ARMv8.2的扩展特性中加入的。 3.3.2 算术与移位指令 1.条件操作码 A64指令集沿用了A32指令集中的条件操作,在PSTATE寄存器中有4个条件标 志位,即N、Z、C、V,如表3-8所示。 表3-8 条件标志位 条件标志位描 述 N 负数标志(上一次运算结果为负值) Z 零结果标志(上一次运算结果为零) C 进位标志(上一次运算结果发生了无符号数溢出) V 溢出标志(上一次运算结果发生了有符号数溢出) 常见的条件操作后缀如表3-9所示。 表3-9 常见的条件操作后缀 后 缀含义(整数运算) 条件标志位条 件 码 EQ 相等Z=1 0b0000 NE 不相等Z=0 0b0001 CS/HS 发生了无符号数溢出C=1 0b0010 CC/LO 没有发生无符号数溢出C=0 0b0011 续表 6 6 ◆嵌入式系统开发与应用 后 缀含义(整数运算) 条件标志位条 件 码 MI 负数N=1 0b0100 PL 正数或零N=0 0b0101 VS 溢出V=1 0b0110 VC 未溢出V=0 0b0111 HI 无符号数大于(C==1)&&(Z==0) 0b1000 LS 无符号数小于或等于(C==0)||(Z==1) 0b1001 GE 有符号数大于或等于N==V 0b1010 LT 有符号数小于N! =V 0b1011 GT 有符号数大于(Z==0)&&(N==V) 0b1100 LE 有符号数小于或等于(Z==1)||(N! =V) 0b1101 AL 永远执行— 0b1110 NV 永不执行— 0b1111 2.加法与减法指令 1)ADD指令 普通的加法指令有下面几种用法。 . 使用立即数的加法。 . 使用寄存器的加法。 . 使用移位操作的加法。 使用立即数的加法指令格式如下,它的作用是把Xn|SP寄存器的值再加上立即数 imm,把结果写入Xd|SP寄存器。Shift表示可选项,默认表示算术左移操作。 ADD <Xd|SP>, <Xn|SP>, #<imm>{, <shift>} 【例3-6】 以下代码是使用立即数的加法指令示例。 add x0, x1, #1 //把x1 寄存器的值加上立即数1,结果写入x0 寄存器 add x0, x1, #1 LSL 12 //把立即数1 算术左移12 位,然后加上x1 寄存器的值,结果写入x0 寄存器 使用寄存器的加法指令格式如下。这条指令的作用是先对Rm 寄存器做一些扩展, 例如左移操作,然后加上Xn|SP寄存器的值,把结果写入Xd|SP寄存器。 ADD <Xd|SP>, <Xn|SP>, <Rm>{, <extend>{#<amount>}} 【例3-7】 下面是使用寄存器的加法指令。 add x0, x1, x2 //x0=x1+x2 第◆3 章 ARMv8 架构基础知识6 7 add x0, x1, x2, LSL 2 //x0=x1+x2<<2 【例3-8】 下面也是使用寄存器的加法指令。 mov xl, #l mov x2, #0x108a add x0, x1, x2, UXTB add x0, x1, x2, SXTB 上面的示例代码中,第3行的运行结果为0x8B,因为UXTB对X2寄存器的低8位数 据进行了无符号扩展,结果为0x8A,然后加上X1寄存器的值,最终结果为0x8B。在第4行 中,SXTB对X2寄存器的低8位数据进行有符号扩展,结果为0xFFFFFFFFFFFFFF8A,然 后加上X1寄存器的值,最终结果为0xFFFFFFFFFFFFFF8B。 使用移位操作的加法指令的格式如下。这条指令的作用是先对Xm 寄存器做一些 移位操作,然后加上Xn寄存器的值,结果写入Xd寄存器。 ADD <Xd>,<Xn>,<Xm>{,<shift>#<amount>} 【例3-9】 以下代码用于实现移位操作加法。 add x0, x1, x2, LSL 3 //x0=x1+x2<<3 2)ADDS指令 ADDS指令是ADD指令的变种,唯一的区别是指令执行结果会影响PSTATE寄存 器的N、Z、C、V 标志位,例如当计算结果发生无符号数溢出时,C=1。 【例3-10】 下面的代码使用了ADDS指令。 mov x1, 0xFFFFFFFFFFFFFFFF adds x0, x1, #2 mrs x2, nzcv X1的值(0xFFFFFFFFFFFFFFFF)加上立即数2一定会触发无符号数溢出,最终 X0寄存器的值为1,同时设置PSTATE寄存器的C标志位为1。通过读取NZCV 寄存 器进行判断,最终X2寄存器的值为0x20000000,说明第29位的C 字段置1,如图3-7 所示。 图3-7 NZCV 寄存器 3)ADC指令 ADC是进位的加法指令,最终的计算结果需要考虑PSTATE寄存器的C标志位。 ADC指令的格式如下。Xd寄存器的值等于Xn寄存器的值再加上Xm 寄存器的值再加 上C,其中,C表示PSTATE寄存器的C标志位。 ADC <Xd>, <Xn>, <Xm> 6 8 ◆嵌入式系统开发与应用 【例3-11】 如下代码使用了ADC指令。 mov x1, 0xFFFFFFFFFFFFFFFF mov x2, #2 adc x0, x1, x2 mrs x3 nzcv ADC指令的计算过程是0xFFFFFFFFFFFFFFFF+2+C,因为0xFFFFFFFFFFFFFFFF+ 2的过程中已经触发了无符号数溢出,C=1,所以最终计算X0寄存器的值为2。若读取 NZCV寄存器,会发现C标志位也被置位了。 4)SUB指令 普通的减法指令与加法指令类似,也有下面几种用法。 . 使用立即数的减法。 . 使用寄存器的减法。 . 使用移位操作的减法。 使用立即数的减法指令格式如下,它的作用是把Xn|SP 寄存器的值减去立即数 imm,结果写入Xd|SP寄存器。 SUB <Xd|SP>, <Xn|SP>, #<imm>{, <shift>} 【例3-12】 如下代码使用了SUB指令。 Sub x0, x1, #1 //把x1 寄存器的值减去立即数1,结果写入x0 寄存器 sub x0, x1, #1, LSL 12 //把立即数1 算术左移12 位,然后把x1 寄存器中的值减去立即数(1<<12),把结果值 //写入x0 寄存器 使用寄存器的减法指令格式如下。这条指令的作用是先对Rm 寄存器做一些扩展, 例如左移操作,然后Xn|SP 寄存器的值减去Rm 寄存器的值,把结果写入Xd|SP 寄 存器。 SUB <Xd|SP>,<Xn|SP>,<Rm>{,<extend>{#<amount>}} 【例3-13】 如下代码使用了寄存器的减法指令。 sub x0, xl, x2 //x0=x1-x2 sub x0, xl, x2, LSL 2 //x0=x1-x2<<2 【例3-14】 下面的代码也使用了寄存器的减法指令。 mov x1, #1 mov x2, #0x108a sub x0, x1, x2, UXTB sub x0, x1, x2, SXTB 上面的示例代码中,UXTB对X2寄存器的低8位数据进行了无符号扩展,结果为 0x8A,然后计算1-0x8A 的值,最终结果为0xFFFFFFFFFFFFFF77。