在ARM架构中,子程序调用是通过分支链接指令BL(Branch with Link)实现的。这条指令实际上完成了两个关键操作:首先将返回地址保存到链接寄存器(LR,通常是R14),然后将程序计数器(PC)设置为目标子程序的起始地址。
BL指令的标准语法格式为:
armasm复制BL destination
其中destination通常是子程序第一条指令的标签,也可以是程序相对或寄存器相对的表达式。当处理器执行BL指令时:
典型的子程序调用和返回流程如下:
armasm复制main:
MOV r0, #10 ; 设置参数
MOV r1, #20
BL add_numbers ; 调用加法子程序
... ; 继续执行主程序
add_numbers:
ADD r0, r0, r1 ; 执行加法运算
BX lr ; 返回到调用者
ARM架构定义了一套标准的寄存器使用约定,这对于确保不同模块间的互操作性至关重要:
重要提示:在混合使用汇编和高级语言时,必须严格遵守ARM的过程调用标准(Procedure Call Standard),否则会导致难以调试的错误。
ARM提供了几种从子程序返回的方法,各有适用场景:
BX LR:
armasm复制BX lr ; 最常用的返回指令,可切换ARM/Thumb状态
MOV PC, LR:
armasm复制MOV pc, lr ; 早期ARM架构的返回方式,不能切换状态
POP {PC}:
armasm复制PUSH {lr} ; 在子程序开头保存LR
...
POP {pc} ; 从栈中恢复PC实现返回
在Thumb-2代码中,必须使用BX指令返回,因为MOV PC, LR不能自动处理状态切换。
ARM架构最强大的特性之一就是几乎所有的指令都可以条件执行。这种设计大幅提升了代码密度和执行效率,特别适合资源受限的嵌入式系统。
条件执行的基础是CPSR(Current Program Status Register)中的4个状态标志位:
| 标志位 | 名称 | 设置条件 |
|---|---|---|
| N | 负号标志 | 结果为负时置1 |
| Z | 零标志 | 结果为零时置1 |
| C | 进位标志 | 加法进位或减法借位时置1 |
| V | 溢出标志 | 有符号数运算溢出时置1 |
这些标志位通常由以下指令设置:
ARM指令可以通过添加条件码后缀变为条件执行。下表列出了完整的条件码:
| 后缀 | 含义 | 标志位条件 | 典型应用场景 |
|---|---|---|---|
| EQ | 等于 | Z=1 | 比较结果相等 |
| NE | 不等于 | Z=0 | 比较结果不等 |
| CS/HS | 无符号大于等于 | C=1 | 无符号数比较 |
| CC/LO | 无符号小于 | C=0 | 无符号数比较 |
| MI | 负数 | N=1 | 算术运算结果为负 |
| PL | 正数或零 | N=0 | 算术结果非负 |
| VS | 溢出 | V=1 | 有符号数溢出检测 |
| VC | 未溢出 | V=0 | 有符号数运算正常 |
| HI | 无符号大于 | C=1且Z=0 | 无符号数比较 |
| LS | 无符号小于等于 | C=0或Z=1 | 无符号数比较 |
| GE | 有符号大于等于 | N=V | 有符号数比较 |
| LT | 有符号小于 | N≠V | 有符号数比较 |
| GT | 有符号大于 | Z=0且N=V | 有符号数比较 |
| LE | 有符号小于等于 | Z=1或N≠V | 有符号数比较 |
| AL | 无条件执行 | 总是执行 | 默认情况 |
条件执行相比传统的条件分支有两大显著优势:
考虑一个简单的绝对值计算例子:
传统分支方式:
armasm复制CMP r0, #0
BGE positive
RSB r0, r0, #0 ; 取反
positive:
...
条件执行方式:
armasm复制CMP r0, #0
RSBLT r0, r0, #0 ; 仅当负值时执行取反
后者不仅节省了一条指令,还避免了潜在的分支预测惩罚。
让我们通过欧几里得最大公约数(GCD)算法的实现,直观感受条件执行的威力。
c复制int gcd(int a, int b) {
while (a != b) {
if (a > b)
a = a - b;
else
b = b - a;
}
return a;
}
armasm复制gcd:
CMP r0, r1
BEQ end
BLT less
SUB r0, r0, r1
B gcd
less:
SUB r1, r1, r0
B gcd
end:
BX lr
这个实现需要7条指令,每次循环可能执行3-5条指令(取决于分支是否发生)。
armasm复制gcd:
CMP r0, r1
SUBGT r0, r0, r1
SUBLT r1, r1, r0
BNE gcd
BX lr
优化后的版本仅需4条指令,循环体固定执行3条指令,性能提升显著。
假设计算gcd(1,2),两种实现的指令执行情况如下:
传统分支方式:
code复制CMP r0, r1 (1周期)
BEQ end (1周期,不跳转)
BLT less (3周期,跳转)
SUB r1, r1, r0 (1周期)
B gcd (3周期)
CMP r0, r1 (1周期)
BEQ end (3周期)
总计:13周期
条件执行方式:
code复制CMP r0, r1 (1周期)
SUBGT r0, r0, r1 (1周期,不执行)
SUBLT r1, r1, r0 (1周期)
BNE gcd (3周期)
CMP r0, r1 (1周期)
SUBGT r0, r0, r1 (1周期,不执行)
SUBLT r1, r1, r0 (1周期,不执行)
BNE gcd (1周期,不执行)
总计:10周期
条件执行版本不仅代码更紧凑,执行速度也更快。这种优势在复杂算法中会更加明显。
ARMv5TE及更高版本引入了Q标志位,用于饱和算术运算:
典型应用场景:
armasm复制QADD r0, r1, r2 ; 饱和加法
MRS r3, CPSR ; 读取CPSR
TST r3, #0x08000000 ; 检查Q标志位
BNE saturation_occurred
在Thumb-2中,条件执行通过IT(If-Then)指令实现:
armasm复制CMP r0, #5
ITTEE GT ; 4条件指令块(IF-THEN-THEN-ELSE-ELSE)
ADDGT r1, #1 ; 条件成立执行
SUBGT r2, #1 ; 条件成立执行
ADDLE r3, #1 ; 条件不成立执行
SUBLE r4, #1 ; 条件不成立执行
IT指令后最多可跟4条条件指令,指令的条件后缀必须与IT指令指定的条件一致。
减少标志位依赖:连续的算术运算可以只有最后一条指令设置标志位
armasm复制ADD r0, r1, r2 ; 不设置标志位
ADD r3, r4, r5 ; 不设置标志位
ADDS r6, r7, r8 ; 最后设置标志位
合理使用条件执行:简单的if-else结构适合条件执行,复杂逻辑仍需要分支
注意流水线影响:现代ARM处理器有深度流水线,避免频繁修改条件码
Thumb-2代码考虑:在Thumb-2中,条件执行会消耗IT指令空间,需要权衡代码密度
忘记保存LR寄存器:当子程序调用其他子程序时,必须保存LR
armasm复制my_subroutine:
PUSH {lr} ; 必须保存
BL another_sub
POP {pc}
错误的条件码顺序:条件执行指令必须跟在设置标志位的指令后
armasm复制CMP r0, r1
ADD r2, r3, r4 ; 这条指令会破坏标志位!
SUBGT r5, r6, r7 ; 可能基于错误的标志位执行
Thumb模式下的限制:在16位Thumb代码中,只有分支指令可以有条件
寄存器覆盖问题:确保子程序不会破坏调用者需要保留的寄存器(R4-R11)
在实时嵌入式系统中,ISR通常需要极致效率:
armasm复制isr_handler:
PUSH {r0-r3, lr} ; 保存工作寄存器和返回地址
LDR r0, =ISR_BASE ; 加载外设基地址
LDR r1, [r0, #STATUS] ; 读取状态寄存器
TST r1, #TIMEOUT_FLAG
BLNE handle_timeout ; 条件调用处理函数
POP {r0-r3, pc} ; 恢复寄存器并返回
利用多寄存器加载/存储指令实现高效内存操作:
armasm复制; r0=目标地址, r1=源地址, r2=长度(字节数)
copy_block:
LSRS r2, r2, #2 ; 转换为字数
BEQ copy_done
copy_loop:
LDMIA r1!, {r3-r6} ; 一次加载4个字
STMIA r0!, {r3-r6}
SUBS r2, r2, #4
BGT copy_loop
copy_done:
BX lr
PID控制器的条件执行实现:
armasm复制; r0=误差, r1=积分, r2=上次误差
pid_control:
ADD r1, r1, r0 ; 积分项
CMP r1, #MAX_INTEGRAL
MOVGT r1, #MAX_INTEGRAL ; 积分限幅
SUB r3, r0, r2 ; 微分项
MOV r2, r0 ; 保存当前误差
; 条件计算输出
MOV r4, #Kp
MUL r0, r4, r0 ; 比例项
MOV r4, #Ki
MLA r0, r4, r1, r0 ; 加积分项
MOV r4, #Kd
MLA r0, r4, r3, r0 ; 加微分项
BX lr
在嵌入式开发中,理解并熟练应用ARM的子程序调用和条件执行机制,可以显著提升代码效率和系统性能。特别是在实时性要求高的场景中,合理使用这些特性往往能达到事半功倍的效果。