ARM处理器的指令集架构设计是其成功的关键因素之一。与大多数处理器不同,ARM采用了独特的双指令集设计:32位的ARM指令集和16位的Thumb指令集。这种设计在保持高性能的同时,显著提高了代码密度,特别适合嵌入式系统应用。
在嵌入式系统领域,存储器资源往往非常有限。传统的32位ARM指令虽然性能优异,但每条指令占用4字节空间,导致代码体积较大。为了解决这个问题,ARM公司在ARMv4T架构中引入了Thumb指令集。
Thumb指令的核心思想是通过指令压缩来提高代码密度:
这种设计使得Thumb代码的密度比等效的ARM代码高出约30-40%,同时保留了ARM架构的大部分性能优势。在实际应用中,开发者可以灵活地在ARM和Thumb状态间切换,实现性能和代码大小的最佳平衡。
ARM处理器在任一时刻只能处于一种指令集状态,通过CPSR(当前程序状态寄存器)的T位来标识:
状态切换通过专门的跳转指令BX(分支交换)实现:
assembly复制; 从ARM状态切换到Thumb状态
LDR r0, =thumb_code+1 ; +1表示目标地址是Thumb代码
BX r0 ; 切换状态并跳转
; 从Thumb状态切换回ARM状态
LDR r0, =arm_code
BX r0 ; 目标地址最低位为0表示ARM代码
异常处理也会导致状态切换:
Thumb指令采用16位固定长度编码,指令格式高度规整化以提高解码效率。主要的指令格式包括:
code复制15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
| Op | Rs | Rd | Offset5 |
这种格式用于逻辑/算术移位、加减等操作,Offset5字段提供立即数操作数。
code复制15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
| Op | Rb | Rd | Offset5 |
用于寄存器与内存间的数据传输,支持不同的寻址模式和数据类型。
code复制15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
| Cond | Offset8 |
提供基于条件码的短跳转,Offset8为8位有符号偏移量。
Thumb指令集的精简设计使得它在保持较好性能的同时,显著提高了代码密度,成为嵌入式系统开发的理想选择。
ARM处理器的条件执行机制是其架构的一大特色,它基于4个条件码标志位:
N(负标志):当运算结果为负时置1。在补码表示中,最高位为1表示负数。
Z(零标志):当运算结果为零时置1。这对比较操作和循环控制特别重要。
C(进位标志):
V(溢出标志):当有符号数运算结果超出表示范围时置1。
这些标志位由ALU运算指令自动设置,并存储在CPSR寄存器的高4位。Thumb指令集中,只有分支指令可以条件执行,而ARM指令集中几乎所有指令都支持条件执行。
条件执行可以显著减少分支指令的使用,提高代码效率。例如,求两个数的最大值:
assembly复制; 传统方式(使用分支)
CMP r0, r1 ; 比较r0和r1
BLE less_equal ; 如果r0 <= r1则跳转
MOV r2, r0 ; r0较大
B end
less_equal:
MOV r2, r1 ; r1较大
end:
; 使用条件执行
CMP r0, r1 ; 比较r0和r1
MOVGT r2, r0 ; 仅当r0>r1时执行
MOVLE r2, r1 ; 仅当r0<=r1时执行
Thumb指令集虽然条件执行能力有限,但通过条件分支与IT(If-Then)指令组合也能实现类似效果:
assembly复制CMP r0, #10 ; 比较r0和10
ITTEE GT ; If-Then-Then-Else-Else
MOVGT r1, #1 ; r0>10时执行
MOVGT r2, #2 ; r0>10时执行
MOVLE r1, #0 ; r0<=10时执行
MOVLE r2, #0 ; r0<=10时执行
ARM架构定义了16种条件码组合,覆盖了各种比较场景:
| 条件码 | 助记符 | 含义 | 标志位条件 |
|---|---|---|---|
| 0000 | EQ | 相等 | Z=1 |
| 0001 | NE | 不相等 | Z=0 |
| 0010 | CS/HS | 无符号大于等于 | C=1 |
| 0011 | CC/LO | 无符号小于 | C=0 |
| 0100 | MI | 负数 | N=1 |
| 0101 | PL | 正数或零 | N=0 |
| 0110 | VS | 溢出 | V=1 |
| 0111 | VC | 无溢出 | V=0 |
| 1000 | HI | 无符号大于 | C=1且Z=0 |
| 1001 | LS | 无符号小于等于 | C=0或Z=1 |
| 1010 | GE | 有符号大于等于 | N=V |
| 1011 | LT | 有符号小于 | N≠V |
| 1100 | GT | 有符号大于 | Z=0且N=V |
| 1101 | LE | 有符号小于等于 | Z=1或N≠V |
| 1110 | AL | 无条件执行(默认) | - |
理解这些条件码对于编写高效的ARM/Thumb代码至关重要,特别是在优化关键循环和条件判断时。
ARM处理器支持七种工作模式,每种模式都有特定的用途和权限级别:
这些模式通过CPSR的M[4:0]位域控制,在异常发生时自动切换。模式切换会改变可访问的寄存器组和系统权限。
ARM处理器采用寄存器banking技术来加速异常处理。不同模式下,某些寄存器会有独立的副本:
通用寄存器:
特殊寄存器:
程序状态寄存器:
这种设计使得异常处理程序可以立即使用自己的寄存器,无需先保存用户模式的寄存器,大大减少了异常响应时间。
Thumb状态下,寄存器访问有以下特点:
assembly复制MOV r0, r8 ; 将高寄存器r8的值移动到低寄存器r0
ADD r1, r8 ; 将r8加到r1
CMP r2, r9 ; 比较r2和r9
Thumb-2技术扩展了Thumb指令集,增加了更多访问高寄存器的指令,提高了Thumb代码的性能。
重要提示:在异常处理程序中,必须小心处理寄存器的保存与恢复。FIQ模式由于有更多的专用寄存器,通常可以避免保存r8-r12,这使得FIQ处理比IRQ更快。
ARM处理器的异常处理机制是其可靠性的基石。当异常发生时,处理器会执行以下标准化流程:
保存现场:
模式切换:
跳转执行:
异常返回:
ARM处理器支持多种异常类型,每种都有固定的优先级:
异常向量表固定在内存的0x00000000或0xFFFF0000处,包含8个4字节的跳转指令:
| 地址 | 异常类型 | 进入模式 |
|---|---|---|
| 0x00000000 | 复位 | 管理模式 |
| 0x00000004 | 未定义指令 | 未定义模式 |
| 0x00000008 | 软件中断(SWI) | 管理模式 |
| 0x0000000C | 预取中止 | 中止模式 |
| 0x00000010 | 数据中止 | 中止模式 |
| 0x00000014 | (保留) | - |
| 0x00000018 | IRQ | IRQ模式 |
| 0x0000001C | FIQ | FIQ模式 |
FIQ(快速中断)是ARM异常处理中的一项精妙设计,具有以下优化特性:
FIQ处理程序示例:
assembly复制fiq_handler:
SUB lr, lr, #4 ; 计算返回地址
STMFD sp!, {r0-r7, lr} ; 保存寄存器(实际上r8-r14_fiq不需要保存)
... ; 处理中断
LDMFD sp!, {r0-r7, pc}^ ; 恢复寄存器并返回
相比之下,IRQ处理需要保存更多寄存器,典型响应周期为20-30个时钟周期。
不同异常类型需要不同的返回地址校正,这是因为流水线效应导致异常发生时PC值不同:
| 异常类型 | 返回指令 | ARM状态LR值 | Thumb状态LR值 |
|---|---|---|---|
| BL | MOV pc, lr | PC+4 | PC+2 |
| SWI | MOVS pc, lr_svc | PC+4 | PC+2 |
| 未定义指令 | MOVS pc, lr_und | PC+4 | PC+2 |
| FIQ | SUBS pc, lr_fiq, #4 | PC+4 | PC+4 |
| IRQ | SUBS pc, lr_irq, #4 | PC+4 | PC+4 |
| 预取中止 | SUBS pc, lr_abt, #4 | PC+4 | PC+4 |
| 数据中止 | SUBS pc, lr_abt, #8 | PC+8 | PC+8 |
理解这些校正值对于编写正确的异常处理程序至关重要,错误的返回地址会导致不可预测的行为。
让我们通过几个典型Thumb指令来理解其编码方式:
code复制15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Rd | Rs |
将寄存器Rs的值移动到Rd,只能操作r0-r7。
code复制15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
| 0 | 1 | 1 | 0 | 1 | Rb | Rd | Offset5 |
从内存[Rb + Offset5*4]加载字到Rd,Rb和Rd为r0-r7,Offset5为5位无符号数。
code复制15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
| 1 | 1 | 0 | 1 | Cond | Offset8 |
基于条件码Cond跳转到PC+2+Offset8*2,Offset8为8位有符号数。
寄存器分配策略:
条件执行替代分支:
assembly复制; 传统分支方式
CMP r0, #0
BEQ zero_case
... ; 非零处理
B end
zero_case:
... ; 零处理
end:
; 优化后的条件执行方式
CMP r0, #0
BNE not_zero
... ; 零处理
not_zero:
... ; 公共代码
assembly复制; 保存多个寄存器到栈
PUSH {r0-r3, lr} ; 等价于 STMDB sp!, {r0-r3, lr}
...
POP {r0-r3, pc} ; 恢复寄存器并返回
在实际系统中,通常混合使用ARM和Thumb代码以获得最佳性能/密度平衡:
示例:在C代码中指定函数编译模式
c复制__attribute__((target("thumb"))) void thumb_func() {
// 此函数编译为Thumb代码
}
__attribute__((target("arm"))) void arm_func() {
// 此函数编译为ARM代码
}
assembly复制reset_handler:
; 初始化栈指针
LDR sp, =stack_top
; 设置异常向量
LDR r0, =irq_handler
STR r0, [r1, #0x18] ; 设置IRQ向量
; 进入主程序
B main
irq_handler:
SUB lr, lr, #4 ; 校正返回地址
SRSFD sp!, #0x12 ; 保存LR_irq和SPSR_irq到栈
PUSH {r0-r3, r12} ; 保存可能被破坏的寄存器
... ; 中断处理
POP {r0-r3, r12} ; 恢复寄存器
RFE sp! ; 从栈恢复PC和CPSR
未对齐访问:
异常返回错误:
模式切换问题:
最小化异常延迟:
减少模式切换开销:
缓存友好设计:
在实际项目中,异常处理程序的优化可以显著提高系统响应速度和稳定性。通过合理利用ARM处理器的异常机制,可以构建出高效可靠的嵌入式系统。