启动分析
启动分析
复位篇
- 关闭全局中断,防止初始化过程被打断。
- 关闭 Cache、MMU 和对齐检查,分支预测 ,确保初始化阶段的内存访问可控。
- 设置各模式(IRQ/SYS/SVC)的栈指针,为后续操作提供运行环境,打开中断。
- 跳转到主程序(main),完成启动流程。
- 关闭中断:防止初始化流程被意外打断。
- 关闭 Cache/MMU:避免硬件未就绪时的缓存一致性和地址转换问题。
- 关闭对齐和分支预测:简化代码,减少异常风险。
- 本质目标:在硬件环境未确定时,优先保证控制流的简单性和确定性,待基础初始化完成后再启用优化功能。
注意:
SP指针(堆栈)放在RAM开头的地址,存放变量,临时数据(返回地址,寄存器状态)。
程序入口(start.s)在后面,前面要留一点位置
中断篇
CPU有多个状态,每个状态都有各自的SP指针(堆栈),硬件GIC检测到中断发生时,CPU会切换到IRQ等状态,此状态可以操作MRS/MSR指令,来操作内部寄存器
GIC根据发生中断类型(引脚电平判断),在汇编中来调用对应的几大规定死函数
ARMv7 的 IRQ 处理流程
(1) 硬件行为
- 外设触发中断,GIC 拉高 CPU 的
IRQ引脚。 - CPU 检测到
IRQ信号,识别为普通中断,模式转换。 - 硬件自动:
- 保存
PC到LR_irq,保存CPSR到SPSR_irq。 - 切换到
IRQ模式。 - 跳转到
VBAR + 0x18(假设VBAR = 0x8000,则跳转到0x8018)。
- 保存
(2) 软件配置
开发者需在 0x8018 处放置 IRQ 处理代码(如 IRQ_Handler):
在汇编IRQ_Handler中,
- 保存中断现场(寄存器、返回地址、状态寄存器)。
- 获取当前中断号(从 GIC 的
GICC_IAR寄存器)。 - 调用 C 语言中断处理函数(
system_irqhandler)。 - 清除中断挂起标志(写
GICC_EOIR)。 - 恢复现场并返回main函数。
注意:
system_irqhandler
I.MX6U 的总共使用了 128 个SPI中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID,I.MX6U 的中断源共有 128+32=160
在该函数里,会把r0,r1,r2,r3,堆栈(SP)里面的函数参数(如果有的话)传递给这个函数,通常是中断ID,然后根据main函数里面注册的ID和函数指针对应的函数,来调用
函数指针
typedef int (*Pointer)(int,int); //Pointer等价于类型 int (*)(int,int),int (*)(int,int)是类型名,Pointer是别名
Pointer p = add; //但是这里由于C语言语法的关系,我们不能写成 int (*)(int,int) Pointer 这样的形式
//函数本身又返回一个指向int的指针
typedef int *(*Pointer)(int,int); //Pointer等价于类型 int *(*)(int,int),int *(*)(int,int)是类型名,Pointer是别名
Pointer p = add;

ID为GPIO1_Comb对应的函数指针指向gpio1_io18
ARM架构中函数指针多的参数没被用,在r0等寄存器中会被忽略,所以函数指针可以指向参数不匹配的函数,反正参数(ID)已经没用了

上面没给参数,”= “是初始化
下面括号有参数,是调用
汇编调用,r0寄存器传入IDGPIO1_Comb

IRQ汇编
每次出栈的值会覆盖对应寄存器的值
IRQ_Handler:
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */
mrs r0, spsr /* 读取spsr寄存器 */
push {r0} /* 保存spsr寄存器 */
mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
nceManua.pdf P68 P138
*/
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
*/
push {r0, r1} /* 保存r0,r1 */
cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */
pop {lr} /* 执行完C语言中断服务函数,lr出栈 */
cps #0x12 /* 进入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
pop {r0}
msr spsr_cxsf, r0 /* 恢复spsr */
pop {r0-r3, r12} /* r0-r3,r12出栈 */
pop {lr} /* lr出栈 */
subs pc, lr, #4 /* 将lr-4赋给pc */spsr(备份状态寄存器)

cp15协处理器-r0
根据指令格式复用为GIC控制器
MRC p15, 4, r1, c15, c0, 0 ;读取GIC基地址到r1
GIC构成
GIC分为 分发器(Distributor) 和 CPU接口(CPU Interface),后者通常位于基地址偏移0x2000处
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
\* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
\* 这个中断号来绝对调用哪个中断服务函数
*/模式切换
cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */
pop {lr} /* 执行完C语言中断服务函数,lr出栈 */
cps #0x12 /* 进入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
pop {r0}
msr spsr_cxsf, r0 /* 恢复spsr */
pop {r0-r3, r12} /* r0-r3,r12出栈 */
pop {lr} /* lr出栈 */
subs pc, lr, #4 /* 将lr-4赋给pc */中断发生时的初始模式
- 触发中断时的模式: 当CPU响应IRQ(普通中断)时,硬件会自动切换到 IRQ模式(
0x12),并做以下操作:- 将返回地址保存到
LR_irq(通常是PC + 4或其他偏移,取决于架构)。 - 将CPSR保存到
SPSR_irq。 - 禁用IRQ中断(CPSR的I位=1),防止嵌套中断。
- 将返回地址保存到
- 问题: IRQ模式的栈空间有限(已经存了一些寄存器),且默认禁用中断,不适合处理复杂逻辑(如调用C函数)。
切换到SVC模式的目的
(1) 允许中断嵌套
SVC模式是操作系统内核的默认模式,具有以下优势:
- 可配置中断开关:通过修改CPSR,可重新启用IRQ中断(
CPSIE I),允许更高优先级中断(如FIQ)嵌套。 - 独立的栈空间:SVC模式使用独立的栈指针(
SP_svc),避免与IRQ模式的栈(SP_irq)冲突。
(2) 支持复杂操作
调用C函数
C函数可能依赖标准库或动态内存分配,这些操作需在特权模式下执行(如SVC模式)。
IRQ模式通常仅用于保存现场和快速响应,不适合执行复杂逻辑。
EOIR寄存器
str r0, [r1, #0X10] /* 中断执行完成,写EOIR r1是GIC的CPU接口端基地址 */
pop {r0}
msr spsr_cxsf, r0 /* 恢复spsr (有改动) */关键寄存器:GICC_EOIR
- 全称:End of Interrupt Register(中断结束寄存器)。
- 地址:
GIC基地址 + 0x2000(CPU接口偏移) + 0x10(EOIR偏移)。 - 功能
- 写入中断号后,GIC会清除该中断的active状态,允许该中断再次触发。
- 若不写EOIR:该中断将一直被标记为“处理中”,无法再次触发!
LR寄存器的值(返回地址-执行阶段)
1.PC + 8 是ARM对三级流水线(每条指令4字节)的补偿值,通过硬件保存和软件修正,确保中断能返回到正确指令!lr=pc+8这是硬件规定的,确保通用性(cortex-A)
ARM是取指,译指,执行三级流水线(想象打螺丝的车间),pc 指向的是正在取值的地址,这就是很多书上说的 pc=当前执行指令地址+8 (cortex-A)
所以中断恢复现场的时候pc=lr-4(下一跳执行的指令,原来译指的)
0x1000: MOV R0, #1 ; 执行阶段(正在执行)
0x1004: ADD R1, R2, R3 ; 译码阶段
0x1008: SUB R4, R5, R6 ; 取指阶段(PC指向这里)
0x100C:......
0X1010:lr所在
;实际需返回的地址:0x1004(译码阶段的指令,因执行阶段的指令0x1000已不可中断)问题:
汇编调用IRQ函数是硬件触发的,在IRQ函数里面会调用C函数,C函数里面会根据ID调用中断函数,ID参数是怎么传过来的?代码里面没看到
答:ID存在R0寄存器里,汇编里面隐式传递参数

