03_ARM体系结构_Cortex-A系列

03_ARM体系结构_Cortex-A系列

1. 如何来学习ARM结构体系

内容大纲

  • 1、ARM架构和处理器
  • 2、工具、操作系统和开发板
  • 3、ARM寄存器、数据类型、模式和指令集
  • 4、ARM汇编语言
  • 5、SIMD和NEON
  • 6、ARM缓存(Caches)架构
  • 7、内存管理单元
  • 8、中断处理(Interrupt Handling)
  • 9、异常处理(Exception Handling)
  • 10、启动代码
  • 11、多处理器结构

官方资料

  • 架构资料

2. ARM架构和处理器

3. 相关术语介绍

  • 架构版本演变
    ARMv4 -> ARMv5 -> ARMv6 -> ARMv7

  • 相关术语

    • 流水线:三级流水:取值(Fetch) - 译码(Decode) - 执行(Exe)
    • DSP - 数字信号处理
    • Jazelle:ARM针对java的
    • ThumbEE:Thumb指令是辅助ARM指令的另外一个指令
    • Thumb-2:
    • TrustZone:
    • VFP(Vector Floating-Point):浮点运算
    • Advaced SIMD(NECO):
    • Large Physical Address Extension(LPAE):大物理地址扩展
    • Virtualization:虚拟化
    • Big LITTLE:大小核 - 省电
  • A系列关键技术点

    • 32位RISC处理器,有16个32位的 可见寄存器
    • 哈佛结构(指令和数据分开)
    • Thumb-2 指令支持
    • VFP和NEON可选
    • 向后兼容
    • 4GB虚拟地址空间和最小4GB物理地址(4GB寻址)
    • 内存虚拟页大小 4KB、64KB、1MB 和 16MB
    • 大小端数据访问支持

4. 工具、操作系统和开发板

4.1. 有用的工具

  • QEMU:Linux虚拟机

QEMU是一套由Fabrice Bellard所编写的模拟处理器的自戌软件。它与Bochs,PearPC近似,但其具有某些后两者所
不具备的特性,如高速度及跨平台的特性.。经由kqeu这个开源的加速器,QEMU能模拟至接近真实电脑的速度。

  • BusyBox:做Linux的根文件系统

BusyBox是一个集成了一百多个最常用Iiux命令和工具的软件。BusyBox包含了一些简单的工具,例如ls、cat和
echo等等,还包含了一些更大、更复杂的工具,例如grep、fnd、mount以及telnet。有些人将BusyBox称为Linux
工具里的瑞士军刀。简单的说BusyBoxs就好象是个大工具箱,它集成压缩了Liux的许多工具和命令,也包含了
Android系统的自带的shell。

  • Scratchbox:交叉编译工具

Scratchbox是一个帮助嵌入式Linux跨平台编辉工具的集合,其目的主要是使得嵌入式Linux应用程序开发更加容易,
他也提供了完整的集成工具链以用来跨平台编译集成出一个Liu以发布版。
这个计划最初是由Movial开发以及由Nokia赞助,是符合GNU General Public License(GPL)发布的·

  • uboot:做Linux的bootloader

U-Boot,全称Universal Boot Loader,是遵循GPL条款的开放源码项目。大名鼎鼎的开源Bootloader

  • UEFI and Tianocore

UEF统一可扩展部件接口,它的诞生,以及我们要绕过它。

4.2. ARM软件工具链(ToolChains)

  • 两个工具链

    • GNU 包含gcc
    • ARM 包含armcc
  • 用工具链生成镜像的过程

    • 预处理 编译 汇编 链接

4.2.1. GNU Toolchain

  • GNU工具链既可以用来开发内核也可以用来开发应用程序,包含以下组件

    • GNU make
    • GNU Compiler Collection (GCC)
    • GNU binutils linker,assembler,and other object/library manipylation tools
    • GNU debugger (GDB)
    • GNU build system (autotools)
    • GNU C library (glibc or eglibc)
  • 其他工具

    • addr2line:把程序地址转换位文件名和行号
    • ar:简历、修改、提取归档文件
    • as:主要用来编译GNU C编译器gcc输出的汇编文件
    • ld:GNU链接器
    • nm:列出目标文件中的符号
    • objcopy:文件格式转换。
    • objdump:显示一个或者更多目标文件的信息,主要用来反编译
    • ranllib:产生归档文件索引,并将其保存到这个归档文件中
    • readelf:显示elf格式可执行文件的信息
    • size:列出目标文件每一段的大小及总体的大小
    • strings:打印某个文件的可打印字符串
    • strip:丢弃目标文件中的全部或者特定符号,减小文件体积
  • ubuntu中安装标准工具链

    • sudo apt install gcc g++ gcc-doc
  • ubuntu中安装交叉工具链

    • sudo apt-get install gcc-arm-linux-gnueabi eabi embedded(嵌入式) application(应用) binary(二进制) interface(接口)

gnu官方网址下载:https::/www.linaro.org/downloads

开发板工具包里有:arm-linux-gcc-4.5.1-v6-vfp-20120301.tgz
解压缩之后加入到系统路径底下即可用

4.2.2. ARM 编译工具

ARM Compiler需要付费

5. ARM寄存器模式

5.1. ARM指令集和Thumb指令集

  • ARM指令 - 32位指令集
  • Thumb指令 - 16位指令集(代码密度更高,但性能低)
  • Thumb-2 16位和32位混合指令集( Cortex-A支持)

5.2. CPU组成

  • ALU:逻辑运算单元 (运算器)
  • 控制器
  • 寄存器:CPU内部存储器
  • 内部总线

5.3. 处理器模式

ARM架构有9种处理器模式,8种特权模式,一种非特权模式,即用户模式

模式名称 编码 功能
User(USER) 10000 大部分程序运行时候的非特权模式
FIQ 10001 进入FIQ中断异常
IRQ 10010 进入IRQ中断异常
Supervision(SVC) 10011 管理调用指令被执行或者reset的时候
Monitor(MON) 10110 安全扩展模式,只用于安全
Abort(ABT) 10111 存储访问异常
Hyp(HYP) 11010 虚拟化扩展
Undef(UND) 10011 未定义的指令执行的时候
System(SYS) 11111 特权模式,与用户模式共享寄存器

5.4. 寄存器

  • 通用寄存器(存放通用数据 32bit):R0、R1、R2、R3、R4、R5、R6、R7、R8、R9、R10、R11、R12、
  • 栈指针:R13 (SP)
  • 链接寄存器:R14 (LR) 存储子程序返回地址
  • 程序计数器:R15 (PC)
  • 应用程序状态寄存器/当前程序状态寄存器:APSR/CPSR
  • 已存储程序状态寄存器:SPSR

1、R0 - R12 通用寄存器,放通用数据,32 bit
2、各个模式的R0-R12与USER模式是共享的(除了FIQ,R8-R12),PC,CPSR共享的
3、USR模式没有SPSR

例子
1、程序返回:其实就是 MOV PC,LR (LR -> PC)
2、跳转:BL

  • CPSR 指令格式
    • N:ALU负数
    • Z:ALU零位
    • C:ALU 进位操作
    • V:ALU操作溢出
    • Q:累计饱和instructions
    • J:是否Jazelle状态
    • GE[3:0]:SIMD指令使用
    • IT[7:0]:Thumb-2指令的 if...then条件执行
    • E:操作存储的字节顺序
    • A:是否disable异步abort
    • I:disables IRQ

5.5. 指令流水线Pipeline

  • 三级指令流水线
    取指-译码-执行

  • 扩展到五级流水

    • 指令预读取(决定从哪儿取指令) PreFetch
    • 指令读取(从内存系统中读取指令)
    • 指令译码(解读指令并且生成控制信号)
    • 寄存器读取(提供寄存器的值给操作单元)
    • 分配(分配指令给执行单元)
    • 执行(实际的ALU单元处理)
    • 内存访问(数据的存取)
  • 多处理流水线
    双ALU流水线和超标量流水线(Cortex-A8/A9)

5.6. 分支预测

现在的动态预测 BTAC

  • 返回栈(Stack Return) 预测子程序返回地址
    存在哪里

6. ARM汇编语言

程序 = 数据结构 + 算法

6.1. 导言

  • 向寄存器里添加一个值添加100

X86:add eax,#100
68K:ADD #100,D0
ARM:ADD r0,r0,#100

  • 从一个寄存器指针加载到寄存器

X86:mov eax,DWORD PTR [ebx]
68K:MOVE.L (A0),D0
ARM:ldr r0,[r1]

  • 一个完整的ARM汇编指令格式 Operation(cond)(s) Rd,Rn,Operand2
    • Operation 操作指令 例如ADD
    • cond 条件
    • s 状态
    • Rd 目标寄存器
    • Rn 源寄存器
    • Operand2 后续附加操作

例如

    AREA testhello,CODE,READONLY
    ENTRY
    CODE32
START
    ADD R1,R2,#4
    MOV R5,R1
    END
  • AREA testhello,CODE,READONLY AREA表示一个区域,后面跟的区域名,CODE表示该段放在代码区,READONLY表示只读
  • ENTRY 表示入口
  • CODE32表示用32位指令
  • START 表示程序起使处
  • ADD R1,R2,#4 将R2寄存器里的值加上4给R1
  • MOV R5,R1 将R1里的值给R5

# 后面表示立即数,立即数条件:

上面两个指令,缺少条件指令和状态指令

  • 条件指令

MOVEQ R5,R1 将R1里的值给R5
这里的EQ是条件指令,表示上一条比较语句的成员如果相等的话则移动数据
还有很多条件语句,例如NE不相等CS等,可以查手册

  • 状态指令
    N Negetive
    Z Zero
    C Carry 进位
    V overflow 溢出

关于立即数:https://blog.csdn.net/tabactivity/article/details/90266999

6.2. 寻址方式

  • 立即数寻址
    • ADD R0,R0,#0X3F
  • 寄存器寻址
    • ADD R0,R1,R2
  • 寄存器间接寻址
    • LDR R0,[R1] 把R1存的值当作内存地址,取该内存地址的值放到R0
    • STR R0,[R1] 把R0放到内存地址里
  • 寄存器移位寻址
    • ADD R3,R2,R1,LSL #2 把R1里的数左移两位,加上R2的值给R3
  • 基地址寻址
    • LDR R0,[R1,#4] 将R1里的值+4所得到的值作为内存地址,取内存地址里的值给R0
    • LDR R0,[R1],#4 将R1里的值作为内存地址,取出的值+4给R0
    • LDR R0,[R1,R2] 将R1的值加上R2的值作为地址取出的值给R0
  • 多寄存器寻址
    • LDMIA R0,{R1,R2,R3,R4} 所有寄存器里的值全部加起来赋给R0
  • 相对寻址
    • BL NEXT 跳转 到NEXT标号
    • MOV PC,LR 把LR寄存器的值给PC 也有跳转的作用

熟悉以下几个概念

  • 满堆栈
  • 空堆栈
  • 递增堆栈
  • 递减堆栈

6.3. 算数操作

  • 数学操作
Opcode Operands Description Function
ADC Rd,Rn,Op2 带进位的加 Rd = Rn+Op2+C
ADD Rd,Rn,Op2 Rd = Rn+Op2
MOV Rd,Op2 数据传送 Rd = Op2
MVN Rd,Op2 数据取反传送 Rd = ~Op2
RSB Rd,Rn,Op2 翻转减 Rd = Op2-Rn
RSC Rd,Rn,Op2 带进位的翻转减 Rd = Op2-Rn-!C
SBC Rd,Rn,Op2 带进位的减 Rd = Rn-Op2-!C
SUB Rd,Rn,Op2 Rd = Rn-Op2
MUL Rd,Rm,Rs 32位乘法 Rd = Rm*Rs
MLA Rd,Rm,Rs,Rn 32位累加乘法 Rd = Rm*Rs+Rn
UMULL RdLo,RdHi,Rm,Rs 64位无符号乘法 (RdLo,RdHi)=Rm*Rs
UMLAL RdLo,RdHi,Rm,Rs 64位无符号累加乘法 (RdLo,RdHi)=Rm*Rs+(RdLo,RdHi)
SMULL RdLo,RdHi,Rm,Rs 64位有符号乘法
SMLAL RdLo,RdHi,Rm,Rs 64位有符号累加乘法
  • 逻辑操作
Opcode Operands Description Function
AND Rd,Rn,Op2 逻辑与 Rd=Rn&Op2
BIC Rd,Rn,Op2 位清零(逻辑非) Rd=Rn&~Op2
EOR Rd,Rn,Op2 逻辑异或 Rd=Rn^Op2
ORR Rd,Rn,Op2 逻辑或 `Rd=Rn
  • 比较操作
Opcode Operands Description Function
CMP Rn,Op2 比较 Rn-Op2
CMN Rn,Op2 负数比较 Rn-Op2
TEQ Rn,Op2 测试相等 Rn&Op2
TST Rn,Op2 测试 Rn^Op2

6.4. 内存操作

  • 单寄存器读写指令
Opcode Operands Description Function
LDR Rn,Addr 按照字长(word)读取(从内存) LDR R0,[R1]
LDR R0,[R1,#4]
STR 按照字长(word)写入(到内存)
LDRB 按照字节
STRB 字节
LDRH harfword 半字
STRH 半字
LDRBT 用户模式 字节
STRBT 用户模式 字节
LDRT 用户模式 字
STRT 用户模式 字
LDRSB 有符号字节
LDRSH 有符号半字
  • 多寄存器内存访问指令
Opcode Operands Description Function
LDM 从R0取出地址,然后基于此,将内存的值拷贝到后面的寄存器组,依次递增(看规则) LDMIA R0,
STM STMIA R0,

地址模式
数据块模式:IA (传输后地址加4)、IB (传送前地址加4)、DA (传送后地址减4)、DB (传送前地址减4)
堆栈模式:EA (空递减堆栈)、FD (满递减堆栈)、ED (空递增堆栈)、FA (满递增堆栈)

基地址不能用R15

  • 数据交换指令
Opcode Operands Description Function
SWP Rd,Rn1,[Rn2] 内存和寄存器交换 [Rn2] -> Rd,Rn1 -> [Rn2]
如果Rd和Rn1相同就是直接交换
SWPB 字节交换

6.5. 跳转操作

  • 跳转、状态操作
Opcode Operands Description Function
B 跳转指令 pc <- label
BL 带返回的连接跳转 pc <- label
BX 跳转并切换状态Thumb
BLX 待返回的跳转并切换状态Thumb
  • 状态寄存器操作:把32位指令分为4个域:[7:0]控制位域c,[15:8]扩展位域x,[23:16]状态位域s,[31:24]条件位域f
Opcode Operands Description Function
MRS 把程序状态寄存器的值传送到通用寄存器
MSR CPSR,R0
SPSR,R0
CPSR_c,R0
通用寄存器到程序状态寄存器
  • 异常产生的指令
Opcode Operands Description Function
SWI SWI 0x02 软中断指令
BKPT BKPT 断点中断指令

6.6. 伪指令

6.6.1. 伪指令作用及类别

我们的指令已经可以做各类操作了,但我们操作起来太麻烦了。
比如我现在要设置一个值给寄存器R0,但下次我修改了寄存器R0之后又需要读出来刚才的值,那我们就要先临时保存值到SPSR or CPSR,然后不断切换。
再比如,我们要做一个循环,就要用label结合BL不断进行,但如果我们要循环很多次。
我们就定义了一些类似于带参数的宏的操作一样,来定义我们的伪指令,方便我们更好的实现汇编程序逻辑。伪指令只是在汇编器之前作用,汇编之后会翻译为标准的汇编指令集。
我们又分为ARM汇编伪指令GNU汇编伪指令

6.6.2. 基本常用伪指令

Opcode Operands Description Function
AREA AREA test,CODE,READONLY 声明区域段,数据区,代码区等等
CODE16、CODE32 CODE32 声明以下是32位还是16位指令,注意不是切换arm和thumb模式
ENTRY ENTRY伪指令用于指定汇编程序的入口点。在一个完整的汇编程序中
至少要有一个ENTRY(也可以有多个,当有多个ENTRY时,程序的真正入口点由链接器指定),但在一个源文件里最多只能有一个ENTRY(可以没有)。
END END END伪指令用于通知编译器已经到了源程序的结尾
EQU UARTLCON0 EQU 0x3FFD000 EQU伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define。其中EQU可用"*"代替
EXPORT .global EXPORT伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。EXPORT可用GLOBAL代替。标号在程序中区分大小写,[WEAK]选项声明其他的同名标号优先于该标号被引用
IMPORT 相当于静态引用 IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前的源文件中引用,而且物理当前源文件是否引用该标号,该标号就不会被加入到当前源文件的符号表中
EXTERN 相当于动态引用 EXTERN伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,如果当前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中
GET 相当于引用文件 GET伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理,可以使INCLUDE代替GET
RN SRegister RN R0 RN伪指令用于给一个寄存器定义一个别名

6.6.3. 在汇编语言程序中常用的符号

6.6.3.1. 符号命名原则
  • 符号区分大小写,同名的大、小写符号会被编译器认为是两个不同的符号。
  • 符号在其作用范围内必须唯一·
  • 自定义的符号名不能与系统的保留字相同。
  • 符号名不应与指令或伪指令同名
  • 关于ADS里面可恶的Tab和顶行"ADS Style"
6.6.3.2. 符号、变量

ARM (Thumb)汇编程序所支持的变量有数字变量、逻辑变量和字符串变量

Opcode Operands Description Function
GBLA/GBLL/GBLS GBLA Test3 定义全局数字/逻辑/字符串变量
LBLA/LBLL/LBLS LBAL Test3 声明局部变量
SETA/SETL/SETS Test3 SETA Oxaa 设定变量值 不能有Tab
RLIST RegList RLIST (RO-R5,R8,R10} 将寄存器列表名称定义为RegList,可在ARM指令LDM/STM中通过该名称访问寄存器列表。 不能有Tab

定义变量存储在寄存器中还是内存中?
内存中。

6.6.3.3. 常量
  • 数字常里一般为32位的整数,当作为无符号数时,其取值范围为0232-1,当作为有符号数时,其取值范围为-231231-1。
  • 逻辑常里只有两种取值情况:真或假。
  • 字符串常里为一个固定的字符串,一般用于程序运行时的信息提示
6.6.3.4. 变量代换

程序中的变里可通过代换操作取得一个常里。代换操作符为“$”

Sample
LCLS S1
LCLS S2
S1 SETS“Test!w
S2 SETS“This is a SS1”;字符串变里S2的值为“This is a Test!”

6.6.3.5. 表达式和运算符
  • “+”、“-”、“×”、“/”及“MOD”算术运算符
  • “ROL”、“ROR”、“SHL”及“SHR”移位运算符
  • “AND”、“OR”、“NOT”及“EOR”按位逻辑运算符
    逻辑表达式及运算符
  • “=”、“>”、“<”、“>=”、“C=”、“/e”、“<>”运算符
  • “LAND”、“LOR”、“LNOT”及“LEOR”运算符

6.6.4. 寄存器操作

Opcode Operands Description Function
LDR R0,=0x12 某时候可以提到MOV,MVN 大范围寻址到寄存器-绝对寻址
ADR R0,=as 小范围寻址到寄存器
ADRL 中范围寻址到寄存器

6.6.5. 数据定义伪汇编

Opcode Operands Description Function
DCB name DCB value 用于分配一片连续的字节存储单元并用指定的数据初始化 DCB也可用=代替
DCW 用于分配一片连续的半字存储单元并用指定的数据初始化
DCD 用于分配一片连续的字存储单元并用指定的数据初始化
DCFD 浮点双精度
DCFS 浮点单精度
DCQ 8字节为单位
SPACE test SPACE 100 用于分配一片连续的存储单元,并初始化为0,单位为字节
MAP MAP0x100.;定义结构化内存表首地址的值为0x100。
A FIELD16;定义A的长度为16字节,位置为0x100
B FIELD 32;定义B的长度为32字节,位置为0x110
S FIELD 256;定义S的长度为256字,位置为0x130
用于定义一个结构化的内存表首地址 MAP也可用"^"代替
FIELD 用于定义一个结构化的内存表的数据域 FILED也可用#代替

6.6.6. 控制伪指令

Opcode Operands Description Function
IF ELSE ENDIF 条件语句
WHILE WEND 循环语句
MACRO MEND 宏定义
MEXIT 跳出宏

6.7. 混合编程

6.7.1. 为什么要混合编程

1、可扩展性、可维护性
2、兼容性

6.7.2. 汇编和C、C++混合编程的方式

汇编语言与C/C++的混合编程通常有以下几种方式:

  • 在C/C++代码中入汇编指令。
  • 在汇编程序和C/C++的程序之间进行变里的互访
  • 汇编程序、C/C++程序间的相互调用。

6.7.3. C语言嵌入汇编

格式:__asm [volatile]{instruction} [;instruction]

限制条件

  • 不能直接向PC寄存器赋值,程序跳转要使用B或者BL指令
  • 在使用物理寄存器时,不要使用过于复杂的C表达式,避免物理寄存器冲突
  • R12和R13可能被编译器用来存放中间编译结果,计算表达式值的时候京可能的把R0-R3、R12及R14用于子程序调用,因此避免直接使用这些物理寄存器

6.7.4. C语言调用汇编

C语言调用汇编步骤
1、汇编export
2、C语言定义extern function
3、C语言使用

C语言和汇编语言之间的参数传递是通过对应的用R0-R3来进行传递,即R0传递第一个参数,R1传递第二个参数,多于4个的时候借助栈完成,函数的返回值通过R0来传递、
这个规定叫做ATPCS。

6.7.5. 汇编调用C语言

汇编使用C语言步骤
1、C语言实现函数
2、汇编import函数名
3、BL函数名

int cFun(int a,int b,int c)
{
    return a+b+c;
}


start
    MOV R0,#1
    MOV R1,#2
    MOV R2,#3
    BL cFun
    MOV R4,R0

最终结果通过R0返回,赋给R4

6.7.6. 汇编常用操作

posted @ 2023-12-14 15:10  StarAire  阅读(93)  评论(0编辑  收藏  举报