一、ARM理论相关问题
1. ARM内核中包含的器件及其作用
主要功能单元:
| 器件 | 作用 | 位置(是否在内核中) |
|---|---|---|
| ALU(算术逻辑单元) | 执行算术和逻辑运算 | 内核内部 |
| 寄存器组 | 32个通用寄存器,快速数据存取 | 内核内部 |
| CPSR/SPSR | 当前/保存的程序状态寄存器 | 内核内部 |
| MMU(内存管理单元) | 虚拟内存到物理内存的转换 | 内核内部(Cortex-A) |
| Cache(高速缓存) | L1指令/数据缓存,提高访问速度 | 内核内部 |
| TLB(转换旁路缓冲) | 加速虚拟地址转换 | 内核内部 |
| 流水线 | 3-13级流水线,提高指令吞吐量 | 内核内部 |
| NEON(SIMD单元) | 单指令多数据,加速多媒体处理 | 可选,Cortex-A |
| VFP(浮点单元) | 硬件浮点运算 | 可选,Cortex-A |
| ETM(嵌入式跟踪宏单元) | 实时指令跟踪 | 可选 |
| PMU(性能监控单元) | 性能计数和监控 | 可选 |
| GIC(通用中断控制器) | 集中式中断管理 | 外部(通常集成在SoC) |
2. ARM内核工作模式与异常向量表
ARM工作模式(7种):
| 模式 | 编码 | 用途 | 备注 |
|---|---|---|---|
| User(用户模式) | 0b10000 | 正常程序执行 | 非特权模式 |
| FIQ(快速中断) | 0b10001 | 快速中断处理 | 高优先级中断 |
| IRQ(普通中断) | 0b10010 | 普通中断处理 | 通用外设中断 |
| Supervisor(管理模式) | 0b10011 | 操作系统内核 | 复位/软中断入口 |
| Abort(中止模式) | 0b10111 | 内存访问异常 | 页错误/权限错误 |
| Undefined(未定义模式) | 0b11011 | 未定义指令 | 指令解码失败 |
| System(系统模式) | 0b11111 | 特权操作系统任务 | 使用用户模式寄存器 |
异常向量表:
@ ARM异常向量表(基地址通常为0x00000000或0xFFFF0000)
Vector_Table:
LDR PC, Reset_Addr @ 0x00: 复位异常
LDR PC, Undefined_Addr @ 0x04: 未定义指令异常
LDR PC, SWI_Addr @ 0x08: 软件中断(SWI)
LDR PC, Prefetch_Addr @ 0x0C: 预取中止
LDR PC, DataAbort_Addr @ 0x10: 数据中止
NOP @ 0x14: 保留
LDR PC, IRQ_Addr @ 0x18: IRQ中断
LDR PC, FIQ_Addr @ 0x1C: FIQ中断
作用:
-
异常入口:每个异常类型对应固定的地址
-
快速响应:硬件自动跳转到对应地址
-
模式切换:进入异常时自动切换处理器模式
-
上下文保存:自动保存CPSR到SPSR_<mode>
二、ARM汇编相关问题
1. 什么是立即数?如何判断12位立即数?
立即数:直接编码在指令中的常数数值
ARM中12位立即数的构成 :8位数据 + 4位循环右移值
-
合法立即数 = 8位数据循环右移(2×n)位,n=0-15
-
范围:0-255 × 2^(0-30),步长为2的偶次幂
判断方法:
// C语言判断函数
int is_legal_immediate(uint32_t val) {
uint32_t temp;
for(int i = 0; i < 32; i += 2) {
temp = (val >> i) | (val << (32 - i)); // 循环右移i位
if((temp & 0xFF) == temp) { // 低8位是否等于自身
return 1; // 是合法立即数
}
}
return 0; // 不是合法立即数
}
// 示例:
// 0xFF(合法) -> 0xFF循环右移0位
// 0x104(合法) -> 0x41循环右移30位
// 0x101(不合法) -> 无法用8位表示
2. b, bl, bx指令的区别
| 指令 | 功能 | 用途 | 链接寄存器 |
|---|---|---|---|
| B(Branch) | 无条件跳转 | 局部跳转,±32MB范围 | 不保存返回地址 |
| BL(Branch with Link) | 带链接的跳转 | 函数调用,保存返回地址到LR | LR = 下一条指令地址 |
| BX(Branch and eXchange) | 带状态切换的跳转 | 切换ARM/Thumb状态 | 目标地址最低位=1则切换到Thumb |
@ 示例:
b label @ 简单跳转到label
bl function @ 调用function函数,LR保存返回地址
bx r0 @ 跳转到r0指向的地址,可切换状态
3. ARM内核采用的栈类型
ARM支持四种栈类型:
| 栈类型 | SP变化方向 | 满/空栈 |
|---|---|---|
| FD(Full Descending) | 递减 | 满栈(最常用) |
| ED(Empty Descending) | 递减 | 空栈 |
| FA(Full Ascending) | 递增 | 满栈 |
| EA(Empty Ascending) | 递增 | 空栈 |
ARM标准使用FD栈(满递减栈):
-
满栈:SP指向最后一个入栈的数据
-
递减:SP向低地址增长
-
对应汇编指令:
STMFD/LDMFD
4. CPSR条件标志位置位条件
| 标志位 | 名称 | 置位条件 | 含义 |
|---|---|---|---|
| N | Negative | 结果最高位为1 | 结果为负数 |
| Z | Zero | 结果为0 | 结果为零 |
| C | Carry | 无符号溢出/移位 | 加法进位/减法借位 |
| V | oVerflow | 有符号溢出 | 有符号数溢出 |
| Q | Saturation | 饱和运算溢出 | DSP饱和标志 |
具体规则:
-
N位 :
Rd[31] == 1 -
Z位 :
Rd == 0 -
C位:
-
加法:
CarryOut == 1 -
减法:
Borrow == 0(无借位) -
移位:移出的最后一位
-
-
V位 :有符号溢出,
(A[31]==B[31]) && (Rd[31]!=A[31])
5. ARM汇编与C语言函数调用规范
ARM过程调用标准(AAPCS)
寄存器使用约定:
-
r0-r3:参数传递和返回值
-
r4-r11:被调用者保存
-
r12(IP):内部过程调用暂存
-
r13(SP):栈指针
-
r14(LR):链接寄存器
-
r15(PC):程序计数器
汇编调用C函数:
@ 汇编代码调用C函数 int add(int a, int b)
mov r0, #5 @ 第一个参数
mov r1, #3 @ 第二个参数
bl add @ 调用C函数
@ 返回值在r0中
C调用汇编函数:
// C代码声明
extern int asm_function(int a, int b);
// 调用
int result = asm_function(5, 3);
汇编函数实现:
@ 汇编函数实现
.global asm_function
asm_function:
@ 参数:r0 = a, r1 = b
add r0, r0, r1 @ 计算 a + b
bx lr @ 返回,返回值在r0
参数传递规则:
-
≤4个参数:使用r0-r3
-
>4个参数:剩余参数压栈
-
返回值:
-
32位:r0
-
64位:r0-r1
-
浮点数:s0/d0
-
-
栈对齐:SP必须8字节对齐
三、嵌入式开发相关问题
1. LED点灯过程需要配置的寄存器
以i.MX6 GPIO控制LED为例:
// 1. 使能GPIO时钟(CCM模块)
CCM_CCGR1 |= (3 << 26); // 使能GPIO1时钟
// 2. 配置引脚复用(IOMUXC)
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x5; // 复用为GPIO功能
// 3. 配置引脚电气属性
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0; // 驱动能力、上下拉等
// 4. 配置GPIO方向
GPIO1_GDIR |= (1 << 3); // 设置GPIO1_IO03为输出
// 5. 控制LED亮灭
GPIO1_DR |= (1 << 3); // 输出高电平,LED灭
GPIO1_DR &= ~(1 << 3); // 输出低电平,LED亮
关键寄存器:
-
CCM_CCGRx:时钟门控寄存器
-
IOMUXC_SW_MUX_CTL:引脚复用控制
-
IOMUXC_SW_PAD_CTL:引脚电气属性
-
GPIO_GDIR:GPIO方向寄存器
-
GPIO_DR:GPIO数据寄存器
2. ELF文件格式及各段数据
ELF(Executable and Linkable Format)结构:
ELF文件头
├── 程序头表(Program Header Table)
├── 节头表(Section Header Table)
├── .text段 ← 代码段(机器指令)
├── .data段 ← 已初始化全局/静态变量
├── .bss段 ← 未初始化全局/静态变量(不占文件空间)
├── .rodata段 ← 只读数据(字符串常量等)
├── .symtab段 ← 符号表
├── .strtab段 ← 字符串表
├── .rel.*段 ← 重定位表
└── 其他调试信息段
各段内容:
| 段名 | 内容 | 属性 | 文件偏移 |
|---|---|---|---|
| .text | 可执行代码 | R+X | 固定 |
| .data | 初始化的全局/静态变量 | R+W | 固定 |
| .bss | 未初始化的全局/静态变量 | R+W | 不占文件空间 |
| .rodata | 只读数据(常量字符串) | R | 固定 |
| .heap | 动态分配内存 | R+W | 运行时 |
| .stack | 局部变量、函数调用 | R+W | 运行时 |
3. 链接脚本的作用
1. 定义绝对运行地址
. = 0x87800000; /* 程序必须运行在这个地址 */
为什么重要:
-
i.MX6ULL裸机程序必须从DDR内存开始执行
-
0x87800000 = 0x80000000(DDR起始) + 120MB偏移
-
确保程序加载到正确位置
2. 控制启动代码顺序
.text :
{
obj/start.o /* 强制放在最前面 */
*(.text) /* 其他代码放后面 */
}
为什么重要:
-
ARM要求异常向量表必须在内存最前端
-
start.o包含复位向量、异常处理 -
确保芯片复位后能正确执行
3. 提供BSS段边界(用于内存初始化)
__bss_start = .; /* 标记开始 */
.bss : {*(.bss)} /* BSS段定义 */
__bss_end = .; /* 标记结束 */
为什么重要:
-
BSS段存储未初始化的全局变量
-
启动时需要清零这些变量
-
提供
__bss_start和__bss_end符号供汇编代码使用
链接脚本的作用:将分散编译的.o文件,按照硬件要求(特定地址、特定顺序)组织成能在i.MX6ULL芯片上直接运行的内存映像。