02-ARM汇编基础.md 26 KB


title: ARM汇编基础 tags: [ARM, 汇编, 嵌入式Linux, IMX6ULL, STM32, RK3568] created: 2026-09-17 updated: 2026-09-17

pdf_ref: "正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 第七章 ARM汇编基础"

ARM汇编基础

前置知识: [[01-Cortex-A7架构详解]] | [[STM32学习笔记/01-STM32开发环境搭建]]

一、ARM指令集概述

⚠️ 来源说明:本节(指令集对比、ARM 指令编码格式、寻址模式)不属于《I.MX6U嵌入式Linux驱动开发指南》第七章内容,为扩展知识。原书第七章只讲解 GNU 汇编语法与最常用的 Cortex-A7 汇编指令。

1.1 ARM指令集 vs Thumb指令集

特性 ARM指令集 Thumb指令集 Thumb-2指令集
指令宽度 32位 16位 16/32位混合
寄存器访问 全部R0-R15 低寄存器R0-R7 全部R0-R15
条件执行 所有指令 仅部分指令 所有指令
性能 最高 较低 接近ARM
代码密度
典型应用 启动代码、关键路径 低功耗应用 通用嵌入式

1.2 指令格式

ARM 32位指令格式:

| 31-28 | 27-25 | 24-21 | 20 | 19-16 | 15-12 | 11-0 |
|-------|-------|-------|----|-------|-------|------|
|  cond  |  001  | opcode|  S |  Rn   |  Rd   | Operand2 |
  • cond: 条件码(4位)
  • opcode: 操作码(4位)
  • S: 状态标志更新(1位)
  • Rn: 第一源寄存器(4位)
  • Rd: 目标寄存器(4位)
  • Operand2: 第二操作数(12位)

1.3 寻址模式

寻址模式 语法 示例 说明
立即数寻址 #immediate MOV R0, #0x10 立即数在指令中
寄存器寻址 Rm ADD R0, R1, R2 操作数在寄存器
寄存器间接寻址 [Rm] LDR R0, [R1] 地址在寄存器
基址变址寻址 [Rn, #offset] LDR R0, [R1, #4] 基址+偏移
前索引寻址 [Rn, #offset]! LDR R0, [R1, #4]! 先更新基址
后索引寻址 [Rn], #offset LDR R0, [R1], #4 先访问后更新
相对寻址 label B loop PC相对跳转

二、数据处理指令

2.0 处理器内部数据传输指令(MOV / MRS / MSR)

原书第 7.2.1 节重点讲解的三个数据传输指令:

指令 目的 描述
MOV R0,R1 R0 R1 将 R1 里面的数据复制到 R0 中
MRS R0,CPSR R0 CPSR 将特殊寄存器 CPSR 里面的数据复制到 R0 中
MSR CPSR,R1 CPSR R1 将 R1 里面的数据复制到特殊寄存器 CPSR 里中
MOV R0, R1           @ R0 = R1
MOV R0, #0X12        @ R0 = 0x12,立即数传递到寄存器
MRS R0, CPSR         @ 读特殊寄存器只能用 MRS:R0 = CPSR
MSR CPSR, R0         @ 写特殊寄存器只能用 MSR:CPSR = R0

要点: CPSR/SPSR 只能用 MRS CPSR/SPSR 只能用 MSR,不能使用 MOV。

2.1 数据传输指令

MOV R0, #10          @ R0 = 10
MOV R1, R2           @ R1 = R2
MOV R0, R1, LSL #2   @ R0 = R1 << 2(左移2位)
MVN R0, #0           @ R0 = 0xFFFFFFFF (-1)
MVN R1, #0xFF        @ R1 = 0xFFFFFF00

2.2 算术指令

ADD R0, R1, R2       @ R0 = R1 + R2
ADD R0, R1, #10      @ R0 = R1 + 10
ADC R0, R1, R2       @ R0 = R1 + R2 + C(带进位加法)
SUB R0, R1, R2       @ R0 = R1 - R2
SBC R0, R1, R2       @ R0 = R1 - R2 - !C(带借位减法)
MUL R0, R1, R2       @ R0 = R1 * R2(32位乘法)
UDIV R0, R1, R2      @ R0 = R1 / R2(无符号除法)
SDIV R0, R1, R2      @ R0 = R1 / R2(有符号除法)

原书第 7.2.5 节列出的算术指令为:ADD、ADC、SUB、SBC、MUL、UDIV、SDIV;其中乘除指令在嵌入式配置寄存器时基本用不到,最常用的是加减。

2.3 逻辑指令

AND R0, R1, #0xFF    @ R0 = R1 & 0xFF(按位与)
ORR R0, R1, #0xFF    @ R0 = R1 | 0xFF(按位或)
ORN R0, R1, #0xFF    @ R0 = R1 | (~0xFF)(按位或非)
EOR R0, R1, #0xFF    @ R0 = R1 ^ 0xFF(按位异或)
BIC R0, R1, #0xFF    @ R0 = R1 & ~0xFF(位清除)

原书第 7.2.6 节列出的逻辑指令为:AND、ORR、BIC、ORN、EOR。

2.4 比较指令

CMP R0, R1           @ 更新标志位:R0 - R1
CMN R0, R1           @ 更新标志位:R0 + R1(负数比较)
TST R0, #0xFF        @ 更新标志位:R0 & 0xFF(位测试)
TEQ R0, R1           @ 更新标志位:R0 ^ R1(等值测试)

2.5 乘法指令

MUL R0, R1, R2       @ R0 = R1 * R2
MLA R0, R1, R2, R3   @ R0 = R1 * R2 + R3(乘加)

三、Load/Store指令

3.1 单寄存器传输

LDR R0, [R1]         @ R0 = *R1
LDR R0, [R1, #4]     @ R0 = *(R1+4)
LDR R0, [R1, #4]!    @ 先更新R1=R1+4,再R0=*R1(前索引)
LDR R0, [R1], #4     @ 先R0=*R1,再更新R1=R1+4(后索引)
LDR R0, =0x0209C000  @ 伪指令:加载32位地址

STR R0, [R1]         @ *R1 = R0
STR R0, [R1, #4]     @ *(R1+4) = R0
LDRB R0, [R1]        @ R0 = *(uint8_t*)R1(字节加载)
STRB R0, [R1]        @ *(uint8_t*)R1 = R0(字节存储)
LDRH R0, [R1]        @ R0 = *(uint16_t*)R1(半字加载)
STRH R0, [R1]        @ *(uint16_t*)R1 = R0(半字存储)

LDR/STR 默认按字(32位)读写,加后缀 B 按字节(LDRB/STRB)、加后缀 H 按半字(LDRH/STRH)读写(原书 7.2.2 节)。

3.2 批量寄存器传输

LDM R0, {R1, R2, R3}     @ R1=*R0, R2=*(R0+4), R3=*(R0+8)
STM R0, {R1, R2, R3}     @ *R0=R1, *(R0+4)=R2, *(R0+8)=R3
LDM R0!, {R1-R3}         @ 先加载,然后R0=R0+12

3.3 堆栈操作

PUSH {R0-R3, LR}        @ 压栈(满递减FD):STMDB SP!, {R0-R3, LR}
POP {R0-R3, PC}         @ 出栈:LDMIA SP!, {R0-R3, PC}

原书第 7.2.3 节指出,PUSH/POP 的另一种等价写法是 STMFD SP!LDMFD SP!

STMFD SP!, {R0~R3, R12}   @ R0~R3,R12 入栈
STMFD SP!, {LR}           @ LR 入栈
LDMFD SP!, {LR}           @ 先恢复 LR
LDMFD SP!, {R0~R3, R12}   @ 再恢复 R0~R3,R12

其中 STM = 多存储(Store Multiple),LDM = 多加载(Load Multiple),FD = Full Descending(满递减)。ATPCS 规定 ARM 使用 FD 类型堆栈:SP 指向最后一个入栈的数据,堆栈由高地址向低地址增长。STM/LDM 的寄存器列表中编号小的对应低地址,编号高的对应高地址

原书堆栈示例:设当前 SP = 0x80000000,向下增长,执行 PUSH {R0~R3, R12} 后:

地址          内容
0x80000000    (原栈底,SP 初始处)
0x7FFFFFFC    R12
0x7FFFFFF8    R3
0x7FFFFFF4    R2
0x7FFFFFF0    R1
0x7FFFFFEC    R0        ← SP = 0x7FFFFFEC

再执行 PUSH {LR} 后 SP = 0x7FFFFFE8,LR 存放于 0x7FFFFFE8。出栈时按 POP {LR}POP {R0~R3,R12} 顺序从栈顶(SP 当前指向处)地址依次减小提取数据。

说明:PUSH/POPSTMFD SP!/LDMFD SP! 的别名,二者编码相同;STMFDSTMDB 亦为同一指令的两种写法。

3.4 批量传输寻址模式

⚠️ 来源说明:本小节不属于原书第七章内容,为扩展知识;原书只讲解了 STM/LDM 与 STMFD/LDMFD。

后缀 模式 用途
IA 后递增 连续内存读取
IB 前递增 连续内存写入
DA 后递减 逆序内存读取
DB 前递减 堆栈压入

堆栈类型后缀(FD/FA/ED/EA)用于描述堆栈模型,其中 FD(满递减)为 ARM 默认,对应 STMFD/LDMFD;不要把 FD 与 IA/IB/DA/DB 混为同类后缀。x86 使用的是空递减(ED)模型。

四、跳转指令

4.1 跳转指令详解

B loop              @ 无条件跳转到loop(跳转范围 ±2KB 时可改用 B.W 32位版本)
B.W loop            @ 32位版本跳转指令,跳转范围更大
BX LR               @ 间接跳转,跳转到 Rm 中保存的地址,并切换指令集
BL function         @ 跳转并保存返回地址到LR(用于函数调用)
BLX function        @ 结合BX和BL:跳转到Rm指定地址、保存返回地址、可切换指令集

原书第 7.2.4 节跳转指令表:

指令 描述
B <label> 跳转到 label;若跳转范围超过 ±2KB,可用 B.W <label> 使用 32 位版本
BX <Rm> 间接跳转,跳转到 Rm 中保存的地址处,并且切换指令集
BL <label> 跳转到标号地址,并将返回地址保存在 LR 中
BLX <Rm> 结合 BX 和 BL 的特点,跳转到 Rm 指定地址、保存返回地址、切换指令集

原书示例:汇编初始化 C 运行环境后跳转到 main(B 指令,不返回):

_start:
    ldr sp, =0X80200000   @ 设置栈指针
    b   main              @ 跳转到 main 函数

汇编调用 C 函数(需要返回,用 BL):

    push {r0, r1}              @ 保存 r0,r1
    cps  #0x13                 @ 进入 SVC 模式,允许其他中断再次进入
    bl   system_irqhandler     @ 调用 C 语言中断处理函数
    cps  #0x12                 @ 进入 IRQ 模式
    pop  {r0, r1}
    str  r0, [r1, #0X10]       @ 中断执行完成,写 EOIR

4.2 条件码

条件码 助记符 含义 标志位
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 总是执行 -

4.3 条件执行示例

CMP R0, #0
BNE not_zero
MOV R0, #1          @ R0=0时执行
not_zero:

@ ARM特有:条件执行
CMP R0, #10
MOVEQ R1, #0        @ R0==10时执行
MOVNE R1, #1        @ R0!=10时执行

五、伪指令

5.1 数据定义伪指令

原书第 7.1 节列出的常用伪操作:

伪操作 说明
.byte 定义单字节数据,如 .byte 0x12
.short 定义双字节数据,如 .short 0x1234
.long 定义 4 字节数据,如 .long 0x12345678
.equ 赋值语句,格式 .equ 变量名, 表达式,如 .equ num, 0x12
.align 数据字节对齐,如 .align 4 表示 4 字节对齐
.end 表示源文件结束
.global 定义一个全局符号,格式 .global symbol,如 .global _start
.byte 0x12           @ 定义8位数据
.short 0x1234        @ 定义16位数据
.long 0x12345678     @ 定义32位数据(等价于 .word)
.equ  num, 0x12      @ 定义常量

扩展(GNU as 支持但原书未列).word.hword.asciz.set.weak.balign.macro 等均非原书第 7.1 节所列内容。更多伪操作可参考《ARM Cortex-A(armV7)编程手册 V4.0》第 57 页。

5.2 段控制与符号定义伪指令

.section .testsection @ 用 .section 自定义一个段
.text               @ 代码段
.data               @ 已初始化数据段
.bss                @ 未初始化数据段
.rodata             @ 只读数据段

.global _start      @ 全局符号(类似C的extern)
.weak weak_func     @ 弱符号(可被覆盖,扩展)

段名说明:.text 表示代码段,.data 初始化的数据段,.bss 未初始化的数据段,.rodata 只读数据段。汇编程序的默认入口标号是 _start,也可以在链接脚本中用 ENTRY 指定其他入口点。

5.3 对齐伪指令

.align 4            @ 4字节对齐(原书列举)
.balign 4           @ 4字节对齐(填充NOP,扩展)

5.4 宏定义伪指令(扩展)

.macro SET_GDIR reg, bit
    LDR \reg, =0X0209C004
    LDR r1, =\bit
    STR r1, [\reg]
.endm

SET_GDIR r0, 0x8    @ 使用宏

六、GNU汇编语法

6.0 语句格式与函数定义(原书 7.1 节)

GNU 汇编适用于所有架构,并非 ARM 独享。每行一条语句,每条语句由三个可选部分组成:

label:instruction @ comment
  • label(标号):表示地址位置,任何以“:”结尾的标识符都会被识别为标号,可用来表示指令或数据地址。
  • instruction(指令):汇编指令或伪指令。
  • @ 符号:表示注释,也可以用 C 语言风格的 /* ... */
  • comment:注释内容。

    add:
    MOVS R0, #0X12  @设置 R0=0X12
    

注意! ARM 中的指令、伪指令、伪操作、寄存器名等可以全部大写,也可以全部小写,但是不能大小写混用

GNU 汇编也支持函数,格式为函数名 + 函数体 + 返回语句,返回语句不是必须的:

/* 未定义中断 */
Undefined_Handler:
    ldr r0, =Undefined_Handler
    bx  r0

/* SVC 中断 */
SVC_Handler:
    ldr r0, =SVC_Handler
    bx  r0

其中 bx 就是返回指令。

6.1 文件结构

@ 文件头注释
.syntax unified          @ 统一语法(ARM/Thumb通用)
.cpu cortex-a7           @ 指定CPU

.text                    @ 代码段
.align 2                 @ 4字节对齐

.global _start           @ 入口点声明

_start:                  @ 程序入口
    @ 代码...

6.2 注释语法

@ 单行注释(推荐)
// C风格单行注释(也支持)
/* 多行注释 C风格 */

6.3 标签语法

loop:                   @ 标签定义
    B loop              @ 跳转到标签

function:               @ 函数标签
    PUSH {LR}           @ 保存返回地址
    POP {PC}            @ 返回

6.4 常量表示

MOV R0, #255            @ 十进制
MOV R0, #0xFF           @ 十六进制
MOV R0, #0b11111111     @ 二进制
MOV R0, #0377           @ 八进制
MOV R0, #'A'            @ 字符常量

七、完整示例分析

📖 来源说明:本节示例来自原书第八章 汇编 LED 灯实验第十三章 BSP 工程管理实验imx6ul.lds 链接脚本),不属于第七章内容,此处作为第七章汇编指令的综合应用。

7.1 LED灯汇编驱动

@ LED灯汇编驱动 - IMX6ULL(原书第 8.3 节 led.s)
@ 功能:点亮GPIO1_IO03连接的LED
@ 文件:led.s

.global _start

_start:
    @ ========================================
    @ 步骤1:使能GPIO1时钟
    @ ========================================
    LDR R0, =0X020C4068    @ CCGR0寄存器地址
    LDR R1, =0XFFFFFFFF    @ 所有时钟使能
    STR R1, [R0]           @ 写入CCGR0

    LDR R0, =0X020C406C    @ CCGR1寄存器地址
    STR R1, [R0]           @ 写入CCGR1

    LDR R0, =0X020C4070    @ CCGR2寄存器地址
    STR R1, [R0]           @ 写入CCGR2

    LDR R0, =0X020C4074    @ CCGR3寄存器地址
    STR R1, [R0]           @ 写入CCGR3

    LDR R0, =0X020C4078    @ CCGR4寄存器地址
    STR R1, [R0]           @ 写入CCGR4

    LDR R0, =0X020C407C    @ CCGR5寄存器地址
    STR R1, [R0]           @ 写入CCGR5

    LDR R0, =0X020C4080    @ CCGR6寄存器地址
    STR R1, [R0]           @ 写入CCGR6

    @ ========================================
    @ 步骤2:设置GPIO1_IO03复用为GPIO1_IO03
    @ ========================================
    LDR R0, =0X020E0068    @ SW_MUX_GPIO1_IO03_BASE
    LDR R1, =0X5           @ MUX_MODE = 5 (GPIO模式)
    STR R1, [R0]           @ 写入复用寄存器

    @ ========================================
    @ 步骤3:设置GPIO1_IO03的IO属性
    @ ========================================
    @ bit [16]:  0  HYS关闭
    @ bit [15:14]: 00 默认下拉(PUS)
    @ bit [13]: 0  keeper功能
    @ bit [12]: 1  pull/keeper使能
    @ bit [11]: 0  关闭开路输出
    @ bit [7:6]: 10 速度100Mhz
    @ bit [5:3]: 110 R0/6驱动能力
    @ bit [0]: 0  低转换率
    LDR R0, =0X020E02F4    @ SW_PAD_GPIO1_IO03_BASE
    LDR R1, =0X10B0        @ IO属性值
    STR R1, [R0]           @ 写入属性寄存器

    @ ========================================
    @ 步骤4:设置GPIO1_IO03为输出
    @ ========================================
    LDR R0, =0X0209C004    @ GPIO1_GDIR寄存器
    LDR R1, =0X0000008     @ bit3=1, 输出模式
    STR R1, [R0]           @ 写入方向寄存器

    @ ========================================
    @ 步骤5:点亮LED0 - 设置GPIO1_IO03输出低电平
    @ ========================================
    LDR R0, =0X0209C000    @ GPIO1_DR寄存器
    LDR R1, =0             @ 输出低电平
    STR R1, [R0]           @ 写入数据寄存器

loop:
    B loop                 @ 死循环,保持LED亮

7.2 代码逐行解释

行号 指令 说明
1 LDR R0, =0X020C4068 伪指令,将CCGR0地址加载到R0
2 LDR R1, =0XFFFFFFFF 伪指令,将0xFFFFFFFF加载到R1
3 STR R1, [R0] 将R1的值存储到R0指向的地址
4 LDR R0, =0X020E0068 加载GPIO复用寄存器地址
5 LDR R1, =0X5 GPIO模式值5
6 STR R1, [R0] 配置GPIO复用功能
7 LDR R0, =0X020E02F4 加载IO属性寄存器地址
8 LDR R1, =0X10B0 IO属性配置值
9 STR R1, [R0] 配置IO属性
10 LDR R0, =0X0209C004 加载GPIO方向寄存器地址
11 LDR R1, =0X00000008 设置bit3为输出
12 STR R1, [R0] 配置GPIO为输出模式
13 LDR R0, =0X0209C000 加载GPIO数据寄存器地址
14 LDR R1, =0 输出低电平值
15 STR R1, [R0] 输出低电平,点亮LED
16 B loop 无限循环,保持状态

7.3 链接脚本(原书第十三章 imx6ul.lds)

/* 链接脚本 - imx6ul.lds */
SECTIONS {
    . = 0X87800000;              /* 链接起始地址(DDR 中,与 U-Boot 一致) */

    .text : {
        obj/start.o              /* 必须把 start.o 放在最前面,保证 _start 位于 0x87800000 */
        *(.text)                 /* 其余代码段 */
    }

    .rodata ALIGN(4) : { *(.rodata*) }   /* 只读数据段 */

    .data ALIGN(4) : { *(.data) }        /* 已初始化数据段 */

    __bss_start = .;                     /* bss 段起始符号 */
    .bss ALIGN(4) : { *(.bss) *(COMMON) }/* 未初始化数据段 + 通用符号 */
    __bss_end = .;                       /* bss 段结束符号 */
}

关键符号 _start / __bss_start / __bss_end

  • _start.global 声明的全局入口标号,中断向量表就放在它前面,位于链接起始地址 0x87800000。
  • __bss_start / __bss_end:链接器生成的 bss 段首尾地址符号,C 代码进入前由汇编循环用 0 清零这段区域,保证未初始化全局变量初值为 0。
  • 注意 .text 段必须把 obj/start.o 放在最前,否则 _start 不会落在 0x87800000。

原书编译命令链(第八章 8.4.1 节):

arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis

八、跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

8.1 IMX6ULL vs STM32 vs RK3568

特性 IMX6ULL STM32F4 RK3568
内核 Cortex-A7 Cortex-M4 Cortex-A55
汇编语法 GNU汇编 ARM汇编 GNU汇编
启动文件 start.S startup_stm32f4xx.s start.S
时钟使能 手动写CCGR寄存器 HAL_RCC_GPIOx_CLK_ENABLE() 手动写CRU寄存器
GPIO配置 手动写IOMUXC+GPIO寄存器 HAL_GPIO_Init() 手动写PMU+GPIO寄存器
延迟实现 简单循环延迟 HAL_Delay() 简单循环延迟
代码效率 中等
开发难度

8.2 汇编语法差异

@ IMX6ULL (GNU汇编)
.syntax unified
.cpu cortex-a7
ldr r0, =0x0209C000
str r1, [r0]

@ STM32 (ARM汇编)
AREA    |.text|, CODE, READONLY
THUMB
LDR R0, =0x40020000
STR R1, [R0]

@ RK3568 (GNU汇编)
.syntax unified
.cpu cortex-a55
ldr x0, =0xFF720000
str w1, [x0]

8.3 寄存器差异

寄存器 IMX6ULL STM32F4 RK3568
GPIO数据 GPIOx_DR GPIOx_ODR GPIOx_DR
GPIO方向 GPIOx_GDIR GPIOx_MODER GPIOx_DDR
GPIO输入 GPIOx_DR GPIOx_IDR GPIOx_EXT_PORT
时钟控制 CCM_CCGRx RCC_AHB1ENR CRU_CLKGATE_CON

九、面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

题目1:ARM汇编中,LDR伪指令和LDR指令有什么区别?

答案

  • LDR指令:真正的加载指令,从内存中读取数据

    LDR R0, [R1]        @ 从R1指向的内存地址加载数据
    
  • LDR伪指令:用于加载32位常量或地址

    LDR R0, =0x12345678 @ 加载32位立即数
    LDR R0, =_start     @ 加载标号地址
    
  • 区别

    1. LDR指令的立即数范围有限(8位循环右移),LDR伪指令可以加载任意32位值
    2. LDR伪指令会被汇编器转换为LDR指令+常量池
    3. LDR伪指令更灵活,但会占用常量池空间

题目2:ARM汇编中,PUSH和STM指令有什么关系?

答案

  • PUSH是STM的封装,等同于STMDB SP!, {reg_list}
  • STM有多种寻址模式,PUSH固定使用前递减满堆栈
  • 示例

    PUSH {R0-R3, LR}     @ 等同于 STMDB SP!, {R0-R3, LR}
    STMDB SP!, {R0-R3, LR}  @ 直接使用STM指令
    
  • 堆栈模型

    • 满递减(FD):ARM默认使用
    • 满递增(FA):某些架构使用
    • 空递减(ED):x86使用
    • 空递增(EA):较少使用

题目3:如何在ARM汇编中实现条件执行?有什么优势?

答案

  • 条件执行:ARM指令集的条件码(cond域)可以条件执行任何指令
  • 优势
    1. 减少分支跳转,提高流水线效率
    2. 减少代码尺寸
    3. 简化编译器生成代码
  • 示例

    @ 传统方式
    CMP R0, #0
    BEQ zero_case
    MOV R1, #1
    B end
    zero_case:
      MOV R1, #0
    end:
    
    @ 条件执行(更简洁)
    CMP R0, #0
    MOVEQ R1, #0
    MOVNE R1, #1
    

题目4:ARM汇编中,什么是位置无关代码?如何实现?

答案

  • 位置无关代码(PIC):代码可以在内存中任意位置运行,无需修改
  • 实现方法
    1. 使用PC相对寻址
    2. 避免使用绝对地址
    3. 使用ADRL或LDR PC相对指令
  • 示例

    @ 位置相关代码(不推荐)
    LDR R0, =_start     @ 加载绝对地址
    
    @ 位置无关代码(推荐)
    ADRL R0, _start     @ 加载相对地址
    LDR R0, [PC, #offset] @ PC相对加载
    
  • 应用场景

    1. Bootloader
    2. 内核启动代码
    3. 动态链接库

题目5:如何在ARM汇编中实现一个简单的函数调用?

答案

  • 函数调用:使用BL指令跳转并保存返回地址
  • 函数实现

    @ 函数定义
    my_function:
      PUSH {LR}           @ 保存返回地址
      PUSH {R4-R7}        @ 保存 callee-saved 寄存器
    
      @ 函数体
      MOV R0, #1          @ 返回值
    
      POP {R4-R7}         @ 恢复寄存器
      POP {PC}            @ 返回
    
    @ 函数调用
    BL my_function          @ 调用函数
    MOV R1, R0              @ 使用返回值
    
  • 调用约定

    1. R0-R3:参数和返回值
    2. R4-R11:callee-saved寄存器
    3. R12:IP,过程间调用暂存
    4. R13:SP,栈指针
    5. R14:LR,链接寄存器
    6. R15:PC,程序计数器

相关知识: [[01-Cortex-A7架构详解]] | [[03-时钟与电源管理]] | [[04-GPIO与中断系统]]

内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第七章 ARM汇编基础(7.1 GNU 汇编语法、7.2.1~7.2.6 常用汇编指令);第七章综合示例来自第八章、第十三章。 参考来源: 正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 最后更新: 2026-09-17