ARM架构中的异常处理
一、不同架构的处理流程
处理流程是一样的。
- 每执行完一条指令都会检查有无中断/异常产生
- 发现有中断/异常产生,开始处理:
- 保存现场
- 分辨异常/中断,调用对应的异常/中断处理函数
- 恢复现场
不同的芯片,不同的架构,在这方面的处理稍有差别:
-
CPU中止当前执行,跳转去执行处理异常的代码:也有差异
- cortex M3/M4在向量表上放置的是函数地址
- cortex A7在向量表上放置的是跳转指令
-
保存/恢复现场:cortex M3/M4是硬件实现的,cortex A7是软件实现的
1.1 cortex M3/M4
参考资料:DDI0403E_B_armv7m_arm.pdf
、ARM Cortex-M3与Cortex-M4权威指南.pdf
、PM0056.pdf
要想理解这个处理流程,需要从向量表说起。
向量,在数学定义里是有方向的量,在程序里可以认为向量就是一个数组,里面有多个项。
在ARM架构里,对于异常/中断,它们的处理入口会整齐地排放在一起。
1.1.1 M3/M4的向量表
M3/M4的向量表中,放置的是具体异常/中断的处理函数的地址。
比如发生Reset
异常时,CPU就会从向量表里找到第1项,得到Reset_Handler函数的地址,跳转去执行。
比如发生EXTI Line 0
中断时,CPU就会从向量表里找到第22项,得到EXTI0_IRQHandler函数的地址,跳转去执行。
- 跳转之前,硬件会保存现场
- 函数执行完毕,返回之后,硬件会恢复现场
中断向量表如下:
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
1.1.2 M3/M4的异常/中断处理流程
发生异常/中断时,硬件上实现了这些事情:
-
保存现场:把被中断瞬间的寄存器的值保存进栈里
-
根据异常/中断号,从向量表中得到函数地址,跳转过去执行
-
函数执行完后,从栈中恢复现场
保存现场、分辨异常/中断、跳转执行,都是硬件实现的。
我们只需要在向量表中,把处理函数的地址填进去就可以了。
硬件承包了大部分的工作。
M3/M4的向量表中,存放的是函数地址。
1.2 cortex A7
参考资料:ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
实际上,以前的S3C2440属于ARM9处理器,它的异常/中断处理流程给cortex A7是一样的。
1.2.1 A7的向量表
A7的向量表中,放置的是某类异常的跳转指令。
比如发生Reset
异常时,CPU就会从向量表里找到第0项,得到b reset
指令,执行后就跳转到reset函数。
比如发生任何的中断时,CPU就会从向量表里找到第6项,得到ldr pc, _irq
指令,执行后就跳转到_irq函数。
- 跳转之前,硬件只会保存CPSR寄存器
- 跳转之后,软件要保存现场
- 函数执行完毕,返回之前,软件恢复现场
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
1.2.2 A7的异常/中断处理流程
发生异常/中断时,硬件上实现了这些事情:
-
CPU切换到对应的异常模式,比如IRQ模式、未定义模式、SVC模式
-
保存被中断时的CPSR到SPSR
- CPSR:current program status register,当前程序状态寄存器
- SRSR:saved program status register,保存的程序状态寄存器
-
跳到这个异常的入口地址去,执行指令,这通常是一条跳转指令
软件要做的事情就比较多了:
- 保存现场
- 分辨异常/中断
- 调用对应的处理函数
- 恢复现场
A7的向量表中,存放的是跳转指令。
二、保存现场
2.1 回顾处理流程
CPU每执行完一条指令都会检查有无中断/异常产生,发现有中断/异常产生,开始处理:
- 保存现场
- 分辨异常/中断,调用对应的异常/中断处理函数
- 恢复现场
对于不用的处理器,具体的处理工作有差别:
- 保存现场:cortex M3/M4里是硬件完成,cortex A7等是软件实现
- 分辨异常/中断:cortex M3/M4里是硬件完成,cortex A7等是软件实现
- 调用处理函数:cortex M3/M4里是硬件来调用,cortex A7等是软件自己去调用
- 恢复现场:cortex M3/M4里是软件触发、硬件实现,cortex A7等是软件实现
不管是硬件还是软件实现,第一步都是保存现场。
2.2 为什么要保存现场
任何程序,最终都会转换为机器码,上述C代码可以转换为右边的汇编指令。
对于这4条指令,它们可能随时被异常打断,怎么保证异常处理完后,被打断的程序还能正确运行?
-
这4条指令涉及R0、R1寄存器,程序被打断时、恢复运行时,R0、R1要保持不变
-
执行完第3条指令时,比较结果保存在程序状态寄存器(PSR)里,程序被打断时、恢复运行时,程序状态寄存器保持不变
-
这4条指令,读取a、b内存,程序被打断时、恢复运行时,a、b内存保持不变
内存保持不变,这很容易实现,程序不越界就可以。
所以,关键在于R0、R1、程序状态寄存器要保持不变(当然不止这些寄存器):
- 在处理异常前,把这些寄存器保存在栈中,这称为保存现场
- 在处理完异常后,从栈中恢复这些寄存器,这称为恢复现场
2.3 保存现场
ARM处理器中有这些寄存器:
在arm中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)。
约定R0-R15寄存器的用途:
-
R0-R3
调用者和被调用者之间传参数 -
R4-R11
函数可能被使用,所以在函数的入口保存它们,在函数的出口恢复它们。R0-R15寄存器介绍如下:
还有一个程序状态寄存器,对于M3/M4它被称为XPSR,对于A7它被称为CPSR,我们简称为PSR。
R0-R15、PSR,就是所谓的现场。
发生异常/中断后,在处理异常/中断前,需要保存现场,难道需要保存所有这些寄存器吗?
不需要!
在C函数中,可以修改R0-R3、R12、R14(LR)以及PSR。如果C函数要用到这些寄存器,就要把它们保存到栈里,在函数结束前在从栈中恢复它们。
这些寄存器被拆分成2部分:调用者保存的寄存器(R0-R3,R12,LR,PSR)、被调用者保存的寄存器(R4-R11)。
比如函数A调用函数B,函数A应该知道:
- R0-R3是用来传参数给函数B的
- 函数B可以肆意修改R0-R3
- 函数A不要指望函数B帮你保存R0-R3
- 保存R0-R3,是函数A的事情
- 对于LR、PSR也是同样的道理,保存它们是函数A的责任
对于函数B:
- 我用到R4-R11中的某一个,我都会在函数入口保存、在函数返回前恢复
- 保证在B函数调用前后,函数A看到的R4-R11保存不变
假设函数B就是异常/中断处理函数,函数B本身能保证R4-R11不变,那么保存现场时,只需要保存这些:
- 调用者保存的寄存器(R0-R3,R12,LR,PSR)
- PC
2.4 对于M3/M4
参考资料:DDI0403E_B_armv7m_arm.pdf
、ARM Cortex-M3与Cortex-M4权威指南.pdf
、PM0056.pdf
1.先硬件保存现场
2.然后调用C函数
C函数执行完后,它返回LR所指示的位置。
难道把LR设置为被中断的程序的地址就行了吗?
如果只是返回LR所指示的地方,硬件帮我们保存在栈里的寄存器,怎么恢复?
M3/M4在调用异常处理函数前,把LR设置为一个特殊的值,这个特殊的值被称为EXC_RETURN。
当PC寄存器的值等于EXC_RETURN时,会触发异常返回机制,简单地说:会从栈里恢复R0-R3,R12,LR,PC,PSR等寄存器。
EXC_RETURN的值,请参考ARM Cortex-M3与Cortex-M4权威指南.pdf
,截图如下:
补充2个知识点:
-
操作模式:M3/M4有两个操作模式
- 处理模式:执行中断服务程序等异常处理时,处于处理模式
- 线程模式:执行普通应用程序代码时,处于线程模式
-
M3/M4有两个SP寄存器:SP_process、SP_main
- 有些RTOS在运行用户程序时会使用SP_process,默认使用SP_main。
2.4 对于A7
它寄存器如下:
处理器有9中模式:User、Sys、FIQ、IRQ、ABT、SVC、UND、MON、HYP。
上图中深色的寄存器,表示该模式下的"Banked"寄存器,比如SPSR寄存器,在很多模式下都有自己的、单独的寄存器。
比如IRQ模式下访问SPSR时,访问到的是IRQ模式下自己的SPSR_irq,别的模式下无法访问SPSR_irq。
比较值得关注的是FIQ模式,名为"快中断",它有很多"Banked"寄存器:R8-R12,SP,LR。
在FIQ模式下,它既然能使用自己的R8-R12,SP,LR,自然不需要去保存被中断的程序的"R8-R12,SP,LR"了。
省去保存这几个寄存器的时间,处理中断时自然就快很多,所以被称为"FIQ"。
从上图也看到,几乎每个模式下都有自己是SP寄存器,意味着这些模式下有自己的栈。
当发生异常时,以IRQ为例:
- CPU会自动切换进入对应的模式,比如进入IRQ模式
- 并且会把被中断是的CPSR保存到SPSR_irq里
所以发生异常/中断时,在保存现场时,只需要保存:
- 调用者保存的寄存器(R0-R3,R12,LR)
- PC