【ARMday02】
ARM的7种工作模式:
- User:非特权模式(用户模式),大部分任务执行在这种模式
- FIQ:(快速中断模式)当一个高优先级(fast)中断产生时将会进入这种模式
- IRQ:(普通中断模式)当一个低优先级(normal)中断产生时将会进入这种模
- Supervisor:当复位或软中断指令执行时将会进入这种模式
- Abort:(终止模式)当存取异常时将会进入这种模式
- Undef:(未定义模式)当执行未定义指令时会进入这种模式
- System:使用和User模式相同寄存器集的特权模式
切换情况:
硬件触发:中断、异常(比如 IRQ/FIQ/Abort/Undefined)会自动切换模式;
软件触发:执行
SWI
指令进入 SVC;手动切换:通过修改 CPSR 模式位。
启动代码的主要任务:
- 初始化异常向量表;
- 初始化各工作模式的栈指针寄存器;
- 开启arm内核中断允许;
- 将工作模式设置为user模式;
- 完成上述工作后,引导程序进入c语言主函数执行;
格式:
- area: 这是最重要的一个伪操作,用于定义一个段。程序、数据、堆栈等都需要被组织在不同的段中。
- reset: 这是你为这个段起的名字。名字 reset 具有很强的暗示性,通常用于表示复位向量段,即CPU上电或复位后首先执行的第一段代码所在的位置。
- code: 指定该段的属性为代码,意味着这个段包含可执行的指令。
- readonly: 指定该段的属性为只读。对于代码段来说,这通常是默认且必须的。
- code32: 表示后续指令使用 32位的 ARM 指令集。
- thumb: 表示后续指令使用 16位的 Thumb 指令集。
指令:
mov
MOV{S}<c> <Rd>, #<const>
MOV{S}<c> <Rd>, <Rm>
MOV instruction Canonical form
- MOV{S} <Rd>, <Rm>, ASR #<n> ASR{S} <Rd>, <Rm>, #<n>
- ASR算数右移n位
- MOV{S} <Rd>, <Rm>, LSL #<n> LSL{S} <Rd>, <Rm>, #<n>
- LSL逻辑左移n位
- MOV{S} <Rd>, <Rm>, LSR #<n> LSR{S} <Rd>, <Rm>, #<n>
- LSR逻辑右移n位
- MOV{S} <Rd>, <Rm>, ROR #<n> ROR{S} <Rd>, <Rm>, #<n>
- ROR循环右移n位
移位量来自寄存器Rs
- MOV{S} <Rd>, <Rm>, ASR <Rs> ASR{S} <Rd>, <Rm>, <Rs>
- MOV{S} <Rd>, <Rm>, LSL <Rs> LSL{S} <Rd>, <Rm>, <Rs>
- MOV{S} <Rd>, <Rm>, LSR <Rs> LSR{S} <Rd>, <Rm>, <Rs>
- MOV{S} <Rd>, <Rm>, ROR <Rs> ROR{S} <Rd>, <Rm>, <Rs>
- MOV{S} <Rd>, <Rm>, RRX RRX{S} <Rd>, <Rm>
基本概念:
<Rd>
:目的寄存器(左值,类似 C 里赋值号左边)<Rm>
/<Rs>
:源寄存器(右值,来自谁)#<const>
/#<n>
:立即数(写死在指令里的常数){S}
:可选的 S 后缀,写成MOVS
。带 S 会更新标志位(APSR 里的 N/Z/C/V)。不带 S 不改标志位。<c>
:可选 条件码(如EQ/NE/GT/...
),满足条件才执行- 移位量#<n>/<Rs> 取值范围 (0 - 31)
lsl:逻辑左移:低位补0,高位丢弃(相当于乘以2)
lsr:逻辑右移:高位补0,低位丢弃(相当于除以2)
ror:循环右移,移出的位从左边绕回
asr:算数右移:往右补符号位,常用于有符号数除以2
ADD(加法指令)
ADD{S}<c> <Rd>, <Rn>, <operand2>
<Rd>
:目的寄存器(结果放哪)。<Rn>
:第一个操作数(通常是寄存器)。<operand2>
:第二操作数(可以是立即数或寄存器,寄存器可带移位)。立即数作为第二操作数:
ADD{S}<c> <Rd>, <Rn>, #<const>
寄存器作为第二操作数寄存器:
ADD{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
寄存器作为第二操作数移位量:
ADD{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
{S}
:可选,写成ADDS
则更新条件标志位(N/Z/C/V)。<c>
:可选条件码(如EQ/NE/...
),指令只有在满足条件时才执行。
ADDS
会更新 APSR 的 N/Z/C/V:
N(Negative):结果的最高位(bit31)。
Z(Zero):结果是否为 0(是则 Z=1)。
C(Carry):无符号加法时的进位(即 32-bit 加法是否有向 33 位进位产生)。
V(Overflow):有符号加法时溢出(例如正数+正数变成负数,或负数+负数变成正数)。
sub(减法指令)
- 立即数作为第二操作数:
- SUB{S}<c> <Rd>, <Rn>, #<const>
- 寄存器作为第二操作数寄存器:
- SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
- 寄存器作为第二操作数移位量:
- SUB{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
SUBS
,会更新 APSR 四个标志:
N(Negative)= 结果的最高位(bit31)。
Z(Zero)= 结果是否为 0(是则 Z=1)。
C(Carry)= 对减法来说,C = 1 当且仅当 没有借位(也就是
Rn ≥ operand2
按无符号比较);如果产生借位,则 C = 0。举例:
0x00000005 - 0x00000003
→ 没有借位 →C=1
。0x00000003 - 0x00000005
→ 需要借位 →C=0
。
V(Overflow)= 有符号减法溢出标志(例如正数减负数等导致符号变化的不合理情况)。
什么是立即数?如何判断某数是否合法为 12 位立即数?
在 ARM 数据处理指令中,第二操作数可以是寄存器,也可以是 立即数。立即数就是直接写在指令里的常量。
12位立即数imm12:把某个数转为2进制,该数必须存在一种循环右移(偶数位),使得移位后高24位全0,低8位即为有效imm8;任何能表示成“一个 8-bit 值向右循环旋转偶数位”的 32-bit 数都能编码成这个 imm12。
0xFF
→ 可表示(imm8=0xFF, rotate=0)0x80000000
→ 可表示(imm8=0x80, rotate=8)0xFFFFFFFF
→ 不能表示(需要MVN r0, #0
或 LDR 伪指令)。
LDR(加载指令)
LDR<c> <Rt>, <label>,用来把内存内容装到寄存器
SDR(存放指令)
将寄存器内容写到内存,与LDR对称
MVN(按位取反移动指令)
- MVN{S}<c> <Rd>, #<const>
- MVN{S}<c> <Rd>, <Rm>{, <shift>}
- MVN{S}<c> <Rd>, <Rm>, <type> <Rs>
bic(bit clear):指定位置清0
- BIC{S}<c> <Rd>, <Rn>, #<const>
- BIC{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
- BIC{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
orr(or):指定位置1
- ORR{S}<c> <Rd>, <Rn>, #<const>
- ORR{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
- ORR{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
条件判断标志NZCV
CPSR寄存器中条件判断标志位
N: 符号标志位:上条指令执行结果最高位bit31为1,则 N = 1, 当结果作为有符号解释时为负值;
Z: 零值标志位:上条指令执行结果为0(即bit0 - bit31 均为0),则 Z = 1;
C: 进位标志位:进行无符号解读,如果在加法过程中进位或者减法时没有借位,则为 C = 1,否则 C = 0
V: 溢出标志位:进行有符号解读,是否发生溢出 -2^31 - 2^31-1(两个正数加得负数,两个负数加得正数)
条件码:eq ge gt le lt al(无条件执行)
equal:等于
not equal:不等于
cmp(compare):比较指令
CMP<c> <Rn>, #<const>
CMP<c> <Rn>, <Rm>{, <shift>}
CMP<c> <Rn>, <Rm>, <type> <Rs>
b bl bx :(跳转指令)
B<c> <label>
b fun <==> ldr pc, =fun
B <label>:无条件跳转到 label,相当于 pc = label
,不保存返回地址。
BL<c> <label>
bl fun
BL <label>:调用时把返回地址写入链接寄存器 LR
(R14),然后跳到目标。常用于函数调用
BX<c> <Rm>
bx lr <==> mov pc, lr
BX <Rm>:BX Rm
从寄存器 Rm
读取目标地址并跳转,跳转到寄存器 Rm 的值,可以用于从函数返回(bx lr
),也可以实现 ARM/Thumb 状态切换。
常见用法:
b fun
→ 死循环跳转,不返回;bl fun
→ 调用函数,返回地址存在lr
;bx lr
→ 从函数返回。
ARM内核采用的栈是哪种栈?
使用的是满减栈,即栈向低地址方向增长,SP指向当前栈顶位置,每次压栈(push)会使SP减小,出栈(pop)会使SP增大。
“满”:SP始终指向当前已经使用的栈顶元素,而不是下一个空闲位置
“递减”栈向低地址方向增长(高地址➡低地址)
在 ARM 的过程调用标准(AAPCS)中,还有一个约定:进入函数接口时,SP 必须保持 8 字节对齐,保证数据访问高效。
ARM 汇编调用 C 函数,或者 C 调用汇编函数时,参数和返回值如何处理?
ARM 遵循 AAPCS(ARM Procedure Call Standard) 约定:
参数传递规则:
前四个参数依次放在
r0、r1、r2、r3
。如果参数超过四个,多余的参数会从 右到左 依次压入栈中,由调用者负责分配。
栈在函数入口时必须保持 8 字节对齐。
返回值规则:
如果返回值是 32 位整型或指针,放在
r0
。如果是 64 位整型或 double,放在
r0:r1
。如果返回值是一个较大的结构体,通常由调用者传入一个隐藏指针,返回值写到该内存地址中。
寄存器保存约定:
调用者需要保存 易变寄存器(caller-saved):
r0–r3, r12, lr
。被调用者需要保存 非易变寄存器(callee-saved):
r4–r11
。
举个例子:
如果 C 调用一个汇编函数 int add3(int a, int b, int c)
:
编译器会把
a, b, c
放到r0, r1, r2
。汇编里直接用
r0, r1, r2
做加法,结果放回r0
。最后
bx lr
返回。
反过来,如果汇编里要调用一个 C 函数:
把参数准备好放在
r0–r3
或栈中。bl func
调用,返回值自动在r0
。
1、什么是RISC、CISC;
RISC 和 CISC 是两种指令集设计理念:
RISC(精简指令集计算机):指令集简单,每条指令执行时间短,寻求“硬件简单 + 软件优化”,代表是 ARM、MIPS。
CISC(复杂指令集计算机):指令集复杂,指令功能丰富,单条指令可以完成复杂操作,代表是 x86。
ARM 作为 RISC 架构,特点是指令长度固定(大多数是 32 位,Thumb 是 16 位),流水线容易优化,功耗低。
2、冯.诺伊曼架构和哈佛架构有何区别?ARM内核属于哪一种?
冯·诺伊曼架构:指令和数据共享一条总线,存储空间统一。优点是硬件简单,缺点是容易产生“瓶颈”。
哈佛架构:指令和数据分开存储、分开总线,可以同时取指和访存,效率更高。
ARM 内核一般采用 改进型哈佛架构:内部取指和数据访问分离(提高并行度),但在外部存储器接口上可以统一成一个总线,兼顾效率和灵活性。
3、ARM内核中都有什么?
ARM 内核主要包括:
通用寄存器:R0–R12;
堆栈指针 SP(R13)、链接寄存器 LR(R14)、程序计数器 PC(R15);
CPSR(当前程序状态寄存器),保存标志位和工作模式;
SPSR(保存程序状态寄存器),用于异常返回时恢复现场;
流水线(典型 3 级:取指、译码、执行);
异常向量表 和 模式切换机制。
可以简单概括为:寄存器文件 + 状态寄存器 + 流水线 + 异常机制。
4、ARM有几种工作模式?
ARM 有 7 种主要工作模式:
User(用户模式):普通应用程序运行;
FIQ(快速中断模式);
IRQ(普通中断模式);
Supervisor(管理模式,操作系统用);
Abort(异常模式,存储器访问错误);
Undefined(未定义指令模式);
System(系统模式,特权模式,运行内核任务)。
其中 User 模式权限最低,其余为特权模式。
5、什么是异常向量表?
异常向量表是 ARM 处理器在发生异常或中断时,跳转执行的入口地址表。
它一般存放在地址
0x00000000
或0xFFFF0000
。每种异常都有固定入口,比如:
Reset:0x00
Undefined:0x04
SWI:0x08
Prefetch Abort:0x0C
Data Abort:0x10
IRQ:0x18
FIQ:0x1C
这样 CPU 在异常发生时,就能根据异常类型跳转到对应入口地址去执行服务例程。