V1.0:初始版本、读完《ARM体系结构与编程》后的一个小总结
时间:2021-10-19
如armv5TEJ
了解下即可;
arm有7种模式
Linux主要用了2中模式;用户模式和系统模式
用户模式:R0-R15、CPSR 17个
系统模式:没有自己的寄存器,和用户的共用
快速中断模式:有自己的R8_fiq-R14_fiq、SPSR_fiq;8个
外部中断模式:有自己的R13_irq(SP)、R14_irq(LR)、SPSR_irq;3个
特权模式:有自己的R13_svc(SP)、R14_svc(LR)、SPSR_svc;3个
数据访问中止模式:有自己的R13_abt(SP)、R14_abt(LR)、SPSR_abt;3个
未定义指令模式:有自己的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个寄存器。
控制处理器的状态,如模式、条件等
基本上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 | 无条件转跳 |
IRQ禁止位;=1,禁止IRQ中断
FIQ进制位;=1,禁止FIQ中断
控制指令执行状态;T = 0,在执行ARM指令
T = 1:
版本>= V4:执行Thumb指令
版本>= V5:强制下一条指令产生未定义指令中断
M[4:0] | 模式 |
---|---|
b10000 | User |
b10001 | FIQ |
b10010 | IRQ |
b10011 | Supervisor |
b10111 | Abort |
b11011 | Undefined |
b11111 | System |
ARM指令都是32位;主要由一下几个部分构成:
一般结构:
其中 花括号里面的是可选,其余是必选。
有11种寻址方式:
寻址方式 | 示例 | 含义 |
---|---|---|
# |
MOV R0,#1 | R0=1 |
MOV R0,R1 | R0=R1 | |
MOV R0,R1,LSL #3 | R0=R1*(2^3) | |
MOV R0,R1,LSL R2 | R0=R1*(2^R2) | |
MOV R0,R1,LSR #3 | R0=R1/(2^3) | |
MOV R0,R1,LSR R2 | R0=R1/(2^R2) | |
MOV R0,R1,ASR #3 | 算数右移3位 | |
MOV R0,R1,ASR R2 | 算数右移R2位 | |
MOV R0,R1,ROR #3 | 循环右移3位 | |
MOV R0,R1,ROR R2 | 循环右移R2位 | |
MOV R0,RRX |
一般都可以跟在指令最后面:
基本上ARM的栈都是满减(FD)
一般一个函数:
stmfd sp! {r1,r2-rx...} ;保存要用到的寄存器旧值
;或
push {r1,r....}!
;然后是中间的具体指令
ldmia sp! {r1,r2-rx...} ;恢复寄存器原来的值
下面仅仅简练列举指令大概意思(具体太多了记不住)、详细请百度。
可以向前、向后跳32M的程序空间:
ARM支持16个协处理器、然后每个处理器又有自己的寄存器。
主要有5条指令:
其中
主要是ARM中的协处理器CP15。因为它管理CPU的存储系统(MMU和TLB)
访问CP15只有2种指令:MCR和MRC。且操作码1都永远为0;也就是开头永远只有以下2种:
op2用于区分同一个编号的不同物理寄存器,否则为0,所以一般情况下,op2也为0。
只读、存储ID编码、cache信息(cache大小、类型)
控制寄存器:控制MMU和存储系统的工作模式等
略(太多了、不记得),用到的时候在搜索把。
实现地址转换(虚拟地址->物理地址)、还有一些权限控制
和MMU相关的CP15的寄存器:
ARM支持以下4种大小(个人觉得应该是ARM32,64位就不一定了)
在ARM中使用2级页表机制。
通常如果单位是段的话、一级页表就可以了。
以页位单位的话需要二级页表。
一级页表使用了 短描述符页表(Short-descriptor translation table);页表项有以下特征:
对于一级页表项:低2位标识自己的状态。
对于二级页表项:低2位标识自己的状态。
访问无效页表会触发指令取指异常或数据取指异常
地址转换过程:
虚拟地址->一级页表项->二级页表项->物理地址
每个内存区域都有自己的权限;内存的权限主要是由页表项(PTE)中的AP、APX、Domain 3个字段来共同控制。
AP、APX:组成成不同的访问权限,主要是对不同模式下内存的权限进行控制;如设置某块内存只能特权模式读写、用户模式只能读等
Domain:ARMMMU可以将内存分成16个域、不同的域由自己的访问权限、由以下3种:
ARM里有3种:
由页表项的 TEX、C、B字段进行设置。
主要用于减少进程切换的开销。
每次切换进程都需要吧TLB进行清空、切换效率较低。
ASID的范围是0-255;
作用:
如果 当前进程的ASID,那么 MMU 在查找 TLB 时, 只会查找 TLB 中具有 相同ASID值 的 TLB行
进程切换的时候、设置了 ASID 的 TLB行 不会被清理掉,当下次切换回来的时候还在、不需要清除所有数据、大大减少切换开销
控制位:CP15的C1的bit[0]
;enabled mmu
MRC p15,0,r0,c1,0,0
ORR r0,#1
MCR p15,0,r0,c1,0,0
快速版的PTE、可以算是页表的cache
当页表的项目该表的时候、需要告诉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的锁定
具体操作还没看懂、这里先了解原理
CPU速度很快、内存速度很慢(相对于CPU)。所以出现了Cache、它的速度略慢于CPU、快于内存,根据局部性原理,所以加入cache能大大提升存储系统的性能。
但事无完美,Cache有以下问题:
最小单位:快(cache line),类似于Linux内存管理里面的页。
所以对于Cache、一个虚拟地址由B和W2部分组成:B=块号、W=块内地址
ARM的协处理器CP15的
C1寄存器的某些位控制着Cache的使能功能;
C7寄存器控制着Cache的清空、无效操作;
C9寄存器控制着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种方法:
从读的角度
当Cache未命中的时候、需要从主存读数据,此时有2种策略:
一个约定;规定了子程序间调用的基本规则。
入口参数:
返回参数:
一般情况的,PC = 当前执行指令 + 2条;ARM是执行完指令后(此时PC已更新:PC = 当前执行指令 + 3条)才查询中断状态
所以:发生IRQ、FIR和数据访问中止异常的时候(PC = 当前执行指令 + 3条)、硬件会将(PC-4)放到对面的LR_mode寄存器、如果要返回还需要再减4;
SUBS PC,LR,#4
但是,但是,但是:
SWI、未定义指令异常、指令预取中止异常:PC = 当前执行指令 + 3条;因为中断是由指令产生的、此时PC还没更新。
连接器需要直到以下信息才能生成对应映像文件
有2种入口地址:
对于加载地址=运行时地址的域、又称为固定域(root region)
使用scatter对映像文件的地址映射进行控制;个人接的和gcc的lds文件功能差不多。
输入域的一些属性:
一个加载时域、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)
}
}