3. ARM汇编基础:寄存器组、指令集分类、寻址方式、Thumb/Thumb-2指令集
好,咱们进入第三章。ARM汇编,这是Bootloader和内核裁剪绕不开的坎儿。很多人一看到汇编就头疼,其实说白了,ARM汇编比x86规整得多。你只要摸清它的寄存器、指令套路和寻址方式,后面看启动代码就跟看说明书一样。
我个人习惯,学汇编先别急着背指令,先把“家底”——寄存器组搞清楚。寄存器是CPU的贴身口袋,数据放口袋里操作最快。
3.1 ARM寄存器组
ARM处理器有37个寄存器,但咱们平时打交道最多的就那16个(R0-R15)。这16个是“可见”的,剩下的用于异常模式,后面讲异常时会提到。
| 寄存器 | 别名 | 功能说明 |
|---|---|---|
| R0-R3 | 参数寄存器 | 传参、返回值。函数调用时前4个参数放这里 |
| R4-R11 | 变量寄存器 | 保存局部变量。被调用函数必须保护R4-R11 |
| R12 | IP (Intra-Procedure) | 用于函数调用时的临时跳转 |
| R13 | SP (Stack Pointer) | 栈指针。指向当前栈顶 |
| R14 | LR (Link Register) | 保存函数返回地址 |
| R15 | PC (Program Counter) | 程序计数器。指向当前指令+8(流水线原因) |
| CPSR | 状态寄存器 | 保存条件标志(N/Z/C/V)、模式位、中断使能位 |
这里有个细节——PC的值。ARM状态下,PC总是指向“当前指令地址+8”。为什么?因为ARM用了三级流水线(取指、译码、执行)。你执行第N条指令时,PC已经指向N+2了。写代码时要注意这个偏移量。
3.2 指令集分类
ARM指令集按功能分,其实就几大类。我整理了个表格,方便你对照着看。
| 类别 | 代表指令 | 用途 |
|---|---|---|
| 数据处理 | MOV, ADD, SUB, AND, ORR | 寄存器间的算术逻辑运算 |
| 数据加载/存储 | LDR, STR, LDM, STM | 内存与寄存器之间的数据搬运 |
| 分支跳转 | B, BL, BX, BLX | 改变程序执行流 |
| 协处理器 | MCR, MRC | 操作系统控制协处理器(如MMU、Cache) |
| 异常产生 | SWI (SVC) | 触发软中断,进入内核态 |
你想想看,Bootloader里最常用的是哪几类?加载/存储和分支跳转。LDR加载外设寄存器地址,B/BL跳转到C函数入口。数据处理反而用得少,因为复杂运算都交给C了。
3.3 寻址方式
ARM的寻址方式,说白了就是“怎么找到操作数”。我把它归纳为8种,但常用的就前5种。
- 立即数寻址:操作数直接写在指令里。
MOV R0, #0xFF— 把0xFF赋给R0 - 寄存器寻址:操作数在寄存器里。
MOV R0, R1— 把R1的值赋给R0 - 寄存器移位寻址:先移位再操作。
MOV R0, R1, LSL #2— R1左移2位后赋给R0 - 寄存器间接寻址:寄存器里存的是地址。
LDR R0, [R1]— 从R1指向的内存读数据到R0 - 基址变址寻址:基址+偏移量。
LDR R0, [R1, #4]— 从R1+4的地址读数据 - 多寄存器寻址:一次操作多个寄存器。
LDMIA R0!, {R1-R4}— 从R0指向的内存连续读4个字 - 堆栈寻址:操作栈。
STMFD SP!, {R0-R3, LR}— 压栈保护 - 相对寻址:PC+偏移量。
B label— 跳转到label处
LDR R0, [R1, R2, LSL #2],R1是页表基址,R2是索引,左移2位是因为每个页表项占4字节。这种写法比手动计算地址高效得多。
3.4 Thumb/Thumb-2指令集
ARM指令每条32位,占空间大。早期嵌入式设备ROM小,ARM推出了16位的Thumb指令集。Thumb指令密度高,但功能受限——只能操作R0-R7,不能条件执行。
后来ARM在Cortex-M系列上推出了Thumb-2。这是个混合指令集,16位和32位指令混着用。Thumb-2既保留了Thumb的高密度,又补上了功能短板。现在Cortex-M处理器只支持Thumb-2,不支持ARM指令。
| 特性 | ARM | Thumb | Thumb-2 |
|---|---|---|---|
| 指令长度 | 32位固定 | 16位固定 | 16/32位混合 |
| 代码密度 | 低 | 高(约节省30%) | 高 |
| 可访问寄存器 | R0-R15 | R0-R7(受限) | R0-R14 |
| 条件执行 | 支持 | 仅B指令支持 | 支持(IT指令) |
| 适用处理器 | Cortex-A/R | ARM7/9/11 | Cortex-M全系列 |
嗯,这里有个常见的坑。有些同学在Cortex-M上写启动代码,直接抄了Cortex-A的汇编,结果芯片不跑。为什么?因为Cortex-M的向量表格式和Cortex-A不一样,而且指令集也不兼容。所以写代码前,先确认你的芯片是Cortex-A还是Cortex-M。
下面这张图展示了ARM指令集的发展脉络和选择逻辑。
最后说个实用技巧。在Bootloader里,经常需要判断当前是ARM状态还是Thumb状态。你可以读CPSR的T位(bit 5)。T=0是ARM,T=1是Thumb/Thumb-2。不过Cortex-M的CPSR里T位永远是1,因为只支持Thumb-2。
info registers cpsr 看一眼T位就明白了。