ARM体系结构与编程总结

V1.0:初始版本、读完《ARM体系结构与编程》后的一个小总结

时间:2021-10-19

基本知识

arm版本变种

  • T:thumb指令集
  • M:长乘法指令集(V5版本后、均带有)
  • E:增强型DSP指令集
  • J:java加速器

如armv5TEJ

了解下即可;

处理器模式

arm有7种模式

  1. 用户模式
  2. 系统模式
  3. 快速中断模式
  4. 外部中断模式
  5. 特权模式
  6. 数据访问中止模式
  7. 未定义指令模式

Linux主要用了2中模式;用户模式和系统模式

寄存器组

  1. 用户模式:R0-R15、CPSR 17个

  2. 系统模式:没有自己的寄存器,和用户的共用

  3. 快速中断模式:有自己的R8_fiq-R14_fiq、SPSR_fiq;8个

  4. 外部中断模式:有自己的R13_irq(SP)、R14_irq(LR)、SPSR_irq;3个

  5. 特权模式:有自己的R13_svc(SP)、R14_svc(LR)、SPSR_svc;3个

  6. 数据访问中止模式:有自己的R13_abt(SP)、R14_abt(LR)、SPSR_abt;3个

  7. 未定义指令模式:有自己的R13_und(SP)、R14_und(LR)、SPSR_und;3个

    用户模式和系统模式共用R-0-R15、SPSR;17个

    除了快速中断模式,其余4种模式均只有自己的R13、R14、SPSR;4*3=12个

    快速中断模式还有自己的R8-R12;5+3 = 8个

一个17 + 12 + 8 + = 37个寄存器。

状态寄存器CPSR

控制处理器的状态,如模式、条件等

条件标志位(高4位)

  • N:=1,表示负数
  • Z:=1,表示结果=0
  • C:加法:=1,发送进位;减法:=0,发送借位
  • V:=1,发送符号位溢出

基本上ARM的每条指令都支持条件执行、可以后面添加一些条件标志,如 BEQ,相等时才转跳;BLT,小于的时候才转跳

4位、有16种组合,其相应的助记符如下、其中b1111保留:

条件吗 助记符 标志 含义
b0000 EQ Z = 1 ==
b0001 NE Z = 0 !=
b0010 CS C = 1 ≥(无符号)
b0011 CC C = 0 <(无符号)
b0100 MI N = 1 < 0
b0101 PL N = 0 ≥ 0
b0110 VS V = 1 溢出
b0111 VC V = 0 未溢出
b1000 HI C=1、Z=0 >(无符号)
b1001 LS C=0、Z=1 ≤(无符号)
b1010 GE N = V ≥(有符号)
b1011 LT N != V <(有符号)
b1100 GT Z=0、N=V >(有符号)
b1101 LE Z=1、N!=V ≤(有符号)
b1110 AL 无条件

总结:

无符号比较

助记符 含义
CS >
CC <
HI
LS

有符号比较

助记符 含义
GT >
LT <
GE
LE

判断

助记符 含义
EQ ==
NE !=
MI <0
PL ≥0
VS 溢出
VC 未溢出
AL 无条件转跳

控制位(低8位)

I[7](IRQ失能位)

​ IRQ禁止位;=1,禁止IRQ中断

F[6](FIQ使能位)

​ FIQ进制位;=1,禁止FIQ中断

T[5](指令执行状态)

​ 控制指令执行状态;T = 0,在执行ARM指令

​ T = 1:

​ 版本>= V4:执行Thumb指令

​ 版本>= V5:强制下一条指令产生未定义指令中断

M[4:0](模式控制)

M[4:0] 模式
b10000 User
b10001 FIQ
b10010 IRQ
b10011 Supervisor
b10111 Abort
b11011 Undefined
b11111 System

指令集

ARM指令构成

ARM指令都是32位;主要由一下几个部分构成:

  1. opcode:指令操作符编码
  2. cond:条件码(前面提到的NE、EQ…)
  3. S:是否影响CPSR的值(可以才指令后加S觉得,如ADDS)
  4. Rd:目标寄存器
  5. Rn:包含第一个操作数的寄存器编码(有的指令有多个源寄存器)
  6. shifter_operand:第二个操作数

一般结构:{}{S} ,,

​ 其中 花括号里面的是可选,其余是必选。

寄存器移位方式

  1. ASR:算数右移
  2. LSL:逻辑左移
  3. LSR:逻辑右移
  4. ROR:循环右移
  5. RRX:拓展循环右移

数据处理指令操作数寻址方式

有11种寻址方式:

寻址方式 示例 含义
# MOV R0,#1 R0=1
MOV R0,R1 R0=R1
, LSL # MOV R0,R1,LSL #3 R0=R1*(2^3)
, LSL MOV R0,R1,LSL R2 R0=R1*(2^R2)
, LSR # MOV R0,R1,LSR #3 R0=R1/(2^3)
, LSR MOV R0,R1,LSR R2 R0=R1/(2^R2)
, ASR # MOV R0,R1,ASR #3 算数右移3位
, ASR MOV R0,R1,ASR R2 算数右移R2位
, ROR # MOV R0,R1,ROR #3 循环右移3位
, ROR MOV R0,R1,ROR R2 循环右移R2位
, RRX MOV R0,RRX

操作内存地址的寻址方式

  1. [Rn, #±offest_12]{!}
    1. 如:LDR R0,[R1,#4];读取R1+4内存单元的字到R0
    2. 如:LDR R0,[R1,#-4]!;读取R1±4内存单元的字到R0,同时R1 = R1 - 4
  2. [Rn, ±Rm]{!}
    1. 如:LDR R0,[R1,R2];读取R1+R2内存单元的字到R0
    2. 如:LDR R0,[R1,R2]!;读取R1+R2内存单元的字到R0,同时R1 = R1 + R2
  3. [Rn, ±Rm,shift #shift_imm]{!}
    1. LDR R0, [R1,R2,LSL #2];R0 = (R1 + R2*2^2)内存单元的值
    2. 后跟!执行完后更新Rn的值
    3. shift 可从上面@寄存器移位方式 进行选取
  4. [Rn], #±offest
    1. 事后访问,先访问[Rn],然后更新Rn
    2. LDR R0,[R1],#4;事后访问,R0=R1,然后R1 = R1 + 4
  5. [Rn], ±Rm
    1. 同上
  6. [Rn], ±Rm, shift, #shitf_imm
    1. 同上

杂类修饰符

一般都可以跟在指令最后面:

  • S:决定指令是否影响CPSR标志位

杂类读写(LDR|STR)

  • SB:带符号字节操作;如LDRSB,将高24位设置成符号位
  • SH:带符号半字操作,如LDRSH,将高16位设置成符号位;
  • H:半字操作,如STRH R0,[R1], #8,将R0的低16存待R1内存单元,然后R1 += 8
  • D:双字操作

批量读写(LDM|STM)

  • IA:事后递增,LDMIA SP!,{R0-R1};将SP、SP+4,2个字读到R0和R1中,然后更新SP(如果SP后没有!,则不会更新SP)
  • IB:事前递增,LDMIB SP,{R0-R1};将SP+4、SP+8,2个字读到R0和R1中,不更新SP(因为SP后没有!)
  • DA:事后递减,LDMDA SP!,{R0-R1};将SP、SP-4,2个字读到R0和R1中,然后更新SP(如果SP后没有!,则不会更新SP)
  • DB:事前递减,LDMDB SP,{R0-R1};将SP-4、SP-8,2个字读到R0和R1中,不更新SP(因为SP后没有!)

栈读写(LDM|STM)

  • FD:满减栈,先赋值,在SP–(这是ARM默认的栈方式);LDMFD
  • ED:空减栈,先SP–,在赋值;最后栈空间有一个单元没用上;STMED
  • FA:满增栈,先赋值,再SP++
  • EA:空增栈,先SP++,再赋值

基本上ARM的栈都是满减(FD)

note

  • LDR R1 [R2] :方向 R1<- [R2]
  • STR R1 [R2]:方向 R1 -> [R2]
  • LDM:LDMIA SP! {R2} 方向:SP -> R2…; (和LDR方向相反)
  • STM:STMFD SP! {R0} 方向:R0… -> SP; (和STR 方向相反)

一般一个函数:

	stmfd sp! {r1,r2-rx...}		;保存要用到的寄存器旧值
	;或
	push {r1,r....}!
	
	;然后是中间的具体指令
	
	ldmia sp! {r1,r2-rx...}		;恢复寄存器原来的值

详细指令集

  • S:决定指令是否影响CPSR标志位,跟在指令最后,如MOVS,ADDS,ADCS…

下面仅仅简练列举指令大概意思(具体太多了记不住)、详细请百度。

转跳指令

​ 可以向前、向后跳32M的程序空间:

  • B:仅转跳
  • BX:转跳,并根据目标地址的bit[0]确定是ARM还是Thumb指令
  • BL:转跳,并将返回地址复制到LR
  • BLX:实现BL+BX的功能

数据传送

  • MOV:MOV{cond}{S} Rd,shift_oprend…
  • MVN:将操作数的反码传输到目标寄存器

逻辑运算

  • ADD:ADD Rd,Rn,operand;Rd = Rn + oprand
  • ADC:带进位加法
  • SUB:减法
  • SBC:带借位减法
  • RSB:逆向检测;RSB Rx,Rx,#0(Rx = 0 - Rx)
  • RSC:带借位逆向减法
  • AND:与
  • BIC:位清除;bit Rx ,Rx, 0x03;将低2位清零
  • EOR:异或
  • ORR:或

比较(影响CPSR标志位)

  • CMP:比较(目标寄存器减源寄存器)status = op1 - op2
  • CMN:比较取负数:status = op1 - (-op2)
  • TST:测试位:status = op1 and op2
  • TEQ:测试等价位:status = op1 eor op2

状态访问

  • MRS:读状态寄存器;MRS R0 ,SPSR
  • MSR:写状态寄存器;MSR R0,CPSR

内存访问

  • LDR{B|BT|H|SB|SH|T}:读取指令
  • STR{B|BT|H|T}:读取指令
  • LDM:批量读取指令:LDM{cond} {!},
    • 花括号里面位可选项
    • 条件码参考前面条件标志位章节
    • 地址模式参考前面批量读写、栈读写章节
    • {!},表示写入后更新Rn的值
  • STM:批量写入;各位用法参考上面LDM

中断指令

  • SWI:软件中断指令;SWI 立即数(24位)
  • BKPT:断点中断指令;用于产生软件断点中断

协处理器指令

​ ARM支持16个协处理器、然后每个处理器又有自己的寄存器。

主要有5条指令:

  1. CDP:协处理器数据操作;通知协处理器完成特定操作,失败会产生指令未定义异常
    1. CDP 协处理器编号,操作码1,目标寄存器(协处理器),源寄存器(协处理器),源操作数(协处理器),操作码2
    2. 如:CDP p5,2,c12,c10,c3,4:通知p5协处理器进行初始化;操作码1=2,目标寄存器=c12,源操作数是c10和c3,操作码2=4
  2. LDC:协处理器数据读取;协处理器从内存读取数据
    1. LCD p6,c4,[R2,#4]:读取内存单元(R2+4)的字数据到协处理器p4的c4寄存器
  3. STC:数据写入;协处理器写数据到内存
    1. STC p8, CR8,[R2,#4];将协处理器p8的CR8寄存器的字数据写到内存单元(R2+4)
  4. MCR:ARM寄存器 -> 协处理器寄存器
    1. 格式:MCR 编号,op1,源操作数(主寄存器),目标寄存器1,目标寄存器2,op2
    2. MCR p14,3,R7,c7,c11,6:操作码1和2为3,6;源操作数放在R7,目标寄存器c7,c11
  5. MRC:ARM寄存器 <- 协处理器寄存器;格式同MCR

杂类指令

  • SWP:交换指令;SWP Rd,Rm,[Rn];将Rn内存内容->Rd,Rm->[Rn],当Rn=Rm则相当于交换内容
    • SWP R1,R1,[R2]:R1寄存器内容和内存单元(R2)交换
  • SWPB:同SWP,只是交换字节

其中

  1. T表示用户模式,B表示读字节,S表示有符号(默认无符号)。
  2. 读无符号数是时候、没用到的高位会清零,如LDRB,寄存器高24位会清零
  3. 读有符号数的时候、会进行符号位拓展(没用到的高位=符号位)

存储系统

主要是ARM中的协处理器CP15。因为它管理CPU的存储系统(MMU和TLB)

CP15

访问CP15只有2种指令:MCR和MRC。且操作码1都永远为0;也就是开头永远只有以下2种:

  1. MCR p15 , 0 , Rx , cx , cx , xx
  2. MRC p15 , 0 , Rx , cx , cx , xx

op2用于区分同一个编号的不同物理寄存器,否则为0,所以一般情况下,op2也为0。

C0寄存器(RO)

只读、存储ID编码、cache信息(cache大小、类型)

C1寄存器

控制寄存器:控制MMU和存储系统的工作模式等

其他寄存器

略(太多了、不记得),用到的时候在搜索把。

MMU

实现地址转换(虚拟地址->物理地址)、还有一些权限控制

和MMU相关的CP15的寄存器:

  1. C1某些位:配置MMU
  2. C2:页表基址
  3. C3:域的一些属性,MMU支持16个域
  4. C5:访问失效的状态
  5. C6:失效的地址
  6. C8:TLB相关
  7. C10:TLB相关

快大小

ARM支持以下4种大小(个人觉得应该是ARM32,64位就不一定了)

  1. 超级段(Supersection):16M
  2. 段(section):1M
  3. 大页(Large Page):64K
  4. 小页(Small Page):4K

页表

在ARM中使用2级页表机制。

通常如果单位是段的话、一级页表就可以了。

以页位单位的话需要二级页表。

一级页表

一级页表使用了 短描述符页表(Short-descriptor translation table);页表项有以下特征:

  1. PTE为32bit
  2. 有2级以上页表
  3. 支持32bit物理地址
  4. 支持4种内存(16M、1M、64K、4K)

对于一级页表项:低2位标识自己的状态。

  1. b00:无效页表
  2. b01:指向二级页表
  3. b10:已映射,bit[18] = 0为段、映射大小是1M(高12位为物理基址)
  4. b10:已映射,bit[18] = 1为超级段、映射大小是16M(高8位物理基址)

二级页表

对于二级页表项:低2位标识自己的状态。

  1. b00:无效页表
  2. b01:已映射,为大页(64K)
  3. b1X:已映射,为小页(4K)

访问无效页表会触发指令取指异常或数据取指异常

地址转换过程:

虚拟地址->一级页表项->二级页表项->物理地址

  1. 一级页表页表项 = 页表基址 |(虚拟地址高12位 >> 20)*4
  2. 二级页表页表项 = 一级页表页表项的二级页表基址 | 虚拟地址[19:12]中间8位
  3. 物理地址 = 二级页表描述符的物理地址基址 | 虚拟地址[11:0]

内存属性

每个内存区域都有自己的权限;内存的权限主要是由页表项(PTE)中的AP、APX、Domain 3个字段来共同控制。

AP、APX:组成成不同的访问权限,主要是对不同模式下内存的权限进行控制;如设置某块内存只能特权模式读写、用户模式只能读等

Domain:ARMMMU可以将内存分成16个域、不同的域由自己的访问权限、由以下3种:

  1. 不可访问:任何访问都会引发异常
  2. 管理者:谁都可以访问、不会引发异常
  3. 用户:使用AP和APX来进行权限控制

内存类型

ARM里有3种:

  1. Normal:还分为可共享和非可共享 、可以被缓存
  2. Device:不可被缓存
  3. Strongly-ordered:不可被缓存

由页表项的 TEX、C、B字段进行设置。

ASID

主要用于减少进程切换的开销。

每次切换进程都需要吧TLB进行清空、切换效率较低。

ASID的范围是0-255;

作用:

  1. 如果 当前进程的ASID,那么 MMU 在查找 TLB 时, 只会查找 TLB 中具有 相同ASID值TLB行

  2. 进程切换的时候、设置了 ASIDTLB行 不会被清理掉,当下次切换回来的时候还在、不需要清除所有数据、大大减少切换开销

使能

控制位:CP15的C1的bit[0]

;enabled mmu
MRC p15,0,r0,c1,0,0
ORR r0,#1
MCR p15,0,r0,c1,0,0

TLB

快速版的PTE、可以算是页表的cache

无效(invalidata)

当页表的项目该表的时候、需要告诉TLB某个表项或者全部标识成无效(即需要重新到页表中读取映射关系)

C8协处理器就是用来清除TLB相干操作的

全部无效

;Rd应该为0	
MCR p15,0,Rd,c8,c7,0	;使cache全部无效
MCR p15,0,Rd,c8,c5,0	;整个 指令cache 无效
MCR p15,0,Rd,c8,c6,0	;整个 数据cache 无效

单个无效

;Rd存放对应虚拟地址
MCR p15,0,Rd,c8,c7,1	;使统一cache某个虚拟地址 无效
MCR p15,0,Rd,c8,c5,1	;指令cache某个虚拟地址 无效
MCR p15,0,Rd,c8,c6,1	;数据cache某个虚拟地址 无效

锁定

就是将地址映射关系存或者说缓存到快表上、这中操作成为块表

C10寄存器用来控制TLB的锁定

具体操作还没看懂、这里先了解原理

cache

CPU速度很快、内存速度很慢(相对于CPU)。所以出现了Cache、它的速度略慢于CPU、快于内存,根据局部性原理,所以加入cache能大大提升存储系统的性能。

但事无完美,Cache有以下问题:

  1. 容量较小,速度快意味着昂贵;怎么样将其映射到比他大得多的存储系统上呢?
  2. 一致性问题,数据被缓存了,但是某些情况下改变了、缓存没即使更新,导致CPU仍使用旧的数据。

最小单位:快(cache line),类似于Linux内存管理里面的页。

所以对于Cache、一个虚拟地址由B和W2部分组成:B=块号、W=块内地址

ARM的协处理器CP15的

​ C1寄存器的某些位控制着Cache的使能功能;

​ C7寄存器控制着Cache的清空、无效操作;

​ C9寄存器控制着Cache的锁定操作;

地址映射和变换

3种映射方式:

  1. 全相联映射和变换方式:任意一个地址都能映射到Cache任意位置(跟去教师上课一样、位置随便坐,先到先得)
  2. 直接映射和变换方式:主存的某一块地址只能映射到Cache的一个特定块中;
    • 如 b = B mod Cb;b为Cache的块号、B为主存的快号,Cb为Cache的容量
  3. 组相联映射和变换方式:前两种方法的折中、调节一个中间对象组(Set)
    1. 对主存和Cache分组,组大小相同
    2. 主存的组->Cache的组:直接映射
    3. 组与组建立映射后、组内全相联映射

一个组的块数成为组容量,当

​ 组容量 = 1 = 直接映射(因为一个组只有一个块、组退化成了块、组间直接映射)

​ 组容量 = Cache块数 = 全相联映射(此时只有一个组、因为所有的块都在这个组、组内全相联映射)

一块内存只能访问Cache的一个特定组、但是组内的块能随便映射。

如:主存 1M;Cache 64K

假设分组大小为 1K

主存有:1M / 1K = 1024 组;Cache有:64K / 1K = 64组

组间直接映射:

​ 主存组号 = 地址 / 1K

​ 组号 = 主存组号 mod 64;

也就是 0K、(0+64)K、(0+2*64)K…等只能映射到第0个组

映射的时候、先找到能映射到的组、在进行具体映射;

组号 = (地址 / 1K) % 组数

在这个组里找一个空闲的进行映射

分类

当CPU更新了Cache的内容,要写回到主存,通常有2种方法:

  1. 写通Cache:更新Cache的时候、同时更新主存
  2. 写回Cache:只更新Cache、更Cache被替换的时候、才更新主存

从读的角度

当Cache未命中的时候、需要从主存读数据,此时有2种策略:

  1. 读分配Cache:写数据未命中的时候,将数据写入主存,读取的时候才缓存
  2. 写分配Cache:写数据未命中的时候,将数据读到Cache,进行修改

ATPCS

一个约定;规定了子程序间调用的基本规则。

寄存器使用规则

  1. R0-R3:用来传递参数、被调用者无需保护这些寄存器
  2. R4-R7:保留寄存器、子程序要保证调用前后寄存器的内容不变(使用前入栈存起来、用完后出栈恢复)
  3. R12:用作子程序间备份寄存器
  4. R13、14、15:分别用作SP、LR、PC

参数传递规则

入口参数:

  1. 参数<=4个:用R0-R3来传递
  2. 参数>4个:多出来的用栈传递、并从右到左入栈

返回参数:

  1. 32位:用R0
  2. 64位:用R0-R1
  3. 浮点:用f0、d0或s0
  4. 更多的位:用内存

异常中断

一般情况的,PC = 当前执行指令 + 2条;ARM是执行完指令后(此时PC已更新:PC = 当前执行指令 + 3条)才查询中断状态

所以:发生IRQ、FIR和数据访问中止异常的时候(PC = 当前执行指令 + 3条)、硬件会将(PC-4)放到对面的LR_mode寄存器、如果要返回还需要再减4;

SUBS	PC,LR,#4

但是,但是,但是:

SWI、未定义指令异常、指令预取中止异常:PC = 当前执行指令 + 3条;因为中断是由指令产生的、此时PC还没更新。

ARM映像文件(ELF)

组成

  1. 域:一个映像文件由多(1-3)个域组成;
  2. 输出端:>=1个 输出段 组成 域;输出端包含一系列属性相同的输入端
  3. 输入段:>=1个 输入段 组成 输出端;输入段包含目标文件的代码和数据

连接器需要直到以下信息才能生成对应映像文件

  1. 分组信息:觉得如何将输入段组成输出段和域
  2. 位置信息:各个域要加载到哪

入口

有2种入口地址:

  1. 初始入口地址:一个映像文件只有一个(条件:必须位于运行时域、加载地址=运行时地址)
  2. 入口地址:由汇编中的ENTRY来定义;标识该段代码通过异常中断进入;可以有多个

对于加载地址=运行时地址的域、又称为固定域(root region)

连接控制文件

使用scatter对映像文件的地址映射进行控制;个人接的和gcc的lds文件功能差不多。

输入域的一些属性:

  • RO-CODE、CODE
  • RO-DATA、CONST
  • RO、TEXT(CODE+CONST)
  • RW-DATA
  • RW、DATA(RW-DATA+RW-CODE)
  • ZI、BSS

一个加载时域、3个连续运行时域:

LR_1 0x08000		;定义加载时域、名称=LR_1 起始地址=0x08000
{
	ER_RO	+0		;定义第一个运行域 起始地址=0x08000
	{
		*(+R0)		;该运行时域包含所有RO属性的输入段
	}
	
	ER_RW +0		;定义第一个运行域 起始地址=上一个域结束地址的下一个地址
	{
		*(+RW)
	}
	
	ER_ZI +0
	{
		*(+ZI)
	}
}

一个加载时域、3个不连续运行时域

LR_1 0x08000		;定义加载时域、名称=LR_1 起始地址=0x08000
{
	ER_RO	+0		;定义第一个运行域 起始地址=0x08000
	{
		*(+R0)		;该运行时域包含所有RO属性的输入段
	}
	
	ER_RW 0x40000	;定义运行域 起始地址=0x40000
	{
		*(+RW)
	}
	
	ER_ZI +0
	{
		*(+ZI)
	}
}

两个加载时域、3个不连续运行时域

LR_1 0x08000		;定义加载时域、名称=LR_1 起始地址=0x08000
{
	ER_RO	+0		;定义第一个运行域 起始地址=0x08000
	{
		*(+R0)		;该运行时域包含所有RO属性的输入段
	}
}

LR_1 0x04000		;定义加载时域、名称=LR_1 起始地址=0x08000
{
	ER_RW +0	;定义运行域 起始地址=0x40000
	{
		*(+RW)
	}
	
	ER_ZI +0
	{
		*(+ZI)
	}
}

你可能感兴趣的:(ARM,arm,嵌入式硬件)