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
操作指令 例如ADDcond
条件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给R1MOV 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存的值当作内存地址,取该内存地址的值放到R0STR R0,[R1]
把R0放到内存地址里
- 寄存器移位寻址
ADD R3,R2,R1,LSL #2
把R1里的数左移两位,加上R2的值给R3
- 基地址寻址
LDR R0,[R1,#4]
将R1里的值+4所得到的值作为内存地址,取内存地址里的值给R0LDR R0,[R1],#4
将R1里的值作为内存地址,取出的值+4给R0LDR 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