异常与中断(上)
文章目录
- 一、异常与中断的概念引入与处理流程
- 1.1 生活中的中断
- 1.2 母亲如何处理中断
- 1.3 ARM系统中异常与中断处理流程
- 二、ARM架构中异常与中断的处理
- 2.1 处理流程
- 2.2 cortex M3/M4
- 2.2.1 M3/M4的向量表
- 2.2.2 M3/M4的异常/中断处理流程
- 2.3 cortex A7
- 2.3.1 A7的向量表
- 2.3.2 A7的异常/中断处理流程
- 三、异常处理深入分析_保存现场
- 3.1 回顾处理流程
- 3.2 为什么要保存现场
- 3.3 保存现场
- 3.4 对于M3/M4
- 2.4.1 硬件保存现场
- 3.4.2 调用C函数
- 3.5 对于A7
一、异常与中断的概念引入与处理流程
1.1 生活中的中断
假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。
问:这个母亲怎么才能知道这个小孩醒?
- 过一会打开一次房门,看婴儿是否睡醒,然后接着看书
- 一直等到婴儿发出声音以后再过去查看,期间都在读书
第一种方法叫做查询方式:
- 优点:简单
- 缺点: 累
如何写程序?
while(1)
{
1 read book(读书)
2 open door(开门)
if(小孩还在睡)
return(继续读书)
else
照顾小孩
}
第二种方法叫中断方式:
- 优点:不累
- 缺点:复杂
如何写程序:
while(1)
{
read book
}
中断服务程序() //核心问题:如何被调用?
{
处理照顾小孩
}
1.2 母亲如何处理中断
我们还是看看母亲被小孩哭声打断如何照顾小孩?
母亲的处理过程
- 平时看书
- 发生了各种声音,如何处理这些声音
- 有远处的猫叫(听而不闻,忽略)
- 门铃声有快递(开门收快递)
- 小孩哭声(打开房门,照顾小孩)
- 母亲的处理
- 只会处理门铃声和小孩哭声
- 先在书中放入书签,合上书(保存现场)
- 去处理 (调用对应的中断服务程序)
- 继续看书(恢复现场)
- 只会处理门铃声和小孩哭声
不同情况,不同处理
- 对于门铃:开门取快件
- 对于哭声:照顾小孩
1.3 ARM系统中异常与中断处理流程
我们将母亲的处理过程抽象化:
- 母亲的头脑相当于CPU
- 耳朵听到声音会发送信号给脑袋
- 声音来源有很多种
- 有远处的猫叫,门铃声,小孩哭声
- 这些声音传入耳朵,再由耳朵传给大脑
- 除了这些可以中断母亲的看书,还有其他情况,比如:
- 身体不舒服
- 有只蜘蛛掉下来
- 对于特殊情况无法回避,必须立即处理
对于arm系统,异常与中断的硬件框图如下:
所有的中断源(按键、定时器等),它们发出的中断汇聚到中断控制器,
再由中断控制器发信号给CPU,告诉它发生了那些紧急情况。
除了这些中断,还有什么可以打断CPU的运行?
- 指令不对
- 数据访问有问题
- reset信号
- 等等,这些都可以打断断CPU,这些被称为异常
- 中断属于一种异常
ARM系统中如何处理异常与中断?重点在于保存现场以及恢复现场,
处理过程如下:
- 保存现场(各种寄存器)
- 处理异常(中断属于一种异常)
- 恢复现场
细化一下,在ARM系统中如何使用异常(中断)?
-
初始化
- 设置中断源,让它可以产生中断
- 设置中断控制器(可以屏蔽某个中断,优先级)
- 设置CPU总开关,使能中断
-
执行其他程序:正常程序
-
产生中断,举例:按下按键—>中断控制器—>CPU
-
cpu每执行完一条指令都会检查有无中断/异常产生
-
发现有中断/异常产生,开始处理:
- 保存现场
- 分辨异常/中断,调用对于异常/中断的处理函数
- 恢复现场
不同的芯片,不同的架构,在这方面的处理稍有差别:
- 保存/恢复现场:cortex M3/M4是硬件实现的,cortex A7是软件实现的
- CPU中止当前执行,跳转去执行处理异常的代码:也有差异
- cortex M3/M4在向量表上放置的是函数地址
- cortex A7在向量表上放置的是跳转指令
二、ARM架构中异常与中断的处理
2.1 处理流程
- 每执行完一条指令都会检查有无中断/异常产生
- 发现有中断/异常产生,开始处理:
- 保存现场
- 分辨异常/中断,调用对应的异常/中断处理函数
- 恢复现场
不同的芯片,不同的架构,在这方面的处理稍有差别:
-
CPU中止当前执行,跳转去执行处理异常的代码:也有差异
- cortex M3/M4在向量表上放置的是函数地址
- cortex A7在向量表上放置的是跳转指令
-
保存/恢复现场:cortex M3/M4是硬件实现的,cortex A7是软件实现的
2.2 cortex M3/M4
参考资料:DDI0403E_B_armv7m_arm.pdf
、ARM Cortex-M3与Cortex-M4权威指南.pdf
、PM0056.pdf
要想理解这个处理流程,需要从向量表说起。
向量,在数学定义里是有方向的量,在程序里可以认为向量就是一个数组,里面有多个项。
在ARM架构里,对于异常/中断,它们的处理入口会整齐地排放在一起。
2.2.1 M3/M4的向量表
M3/M4的向量表中,放置的是具体异常/中断的处理函数的地址。
比如发生Reset
异常时,CPU就会从向量表里找到第1项,得到Reset_Handler函数的地址,跳转去执行。
比如发生EXTI Line 0
中断时,CPU就会从向量表里找到第22项,得到EXTI0_IRQHandler函数的地址,跳转去执行。
- 跳转之前,硬件会保存现场(会将寄存器保存在SP栈中)
- 函数执行完毕,返回之后,硬件会恢复现场
编译程序时,我们会将异常向量表(途中的ROM向量表)先设置好并烧写在Flash上,CPU执行程序时,每执行完一条指令,都会判断是否有中断产生,如果有中断产生的话,则会终止下一条指令的运行,然后跳过去处理该异常/中断,他会首先保存现场,把当前程序所设计的寄存器保存到SP栈中,这是由硬件来实现的,保存到栈中后,还得去分辨一下发生的是哪个异常/中断,会使用异常号/中断号在异常向量表中找到对应项,接着CPU会跳过去执行里面的函数,该函数属于软件,函数执行完毕之后会返回,返回之后会触发硬件把栈中之前保存的内容恢复回去,这样之前被中断的程序就会开始继续运行。
在上图过程中,硬件帮我们做了大部分工作,我们只需要提供一个向量表中的C函数,给某一个异常/中断提供C函数即可。
; 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
2.2.2 M3/M4的异常/中断处理流程
发生异常/中断时,硬件上实现了这些事情:
-
保存现场:把被中断瞬间的寄存器的值保存进栈里
-
根据异常/中断号,从向量表中得到函数地址,跳转过去执行
-
函数执行完后,从栈中恢复现场
保存现场、分辨异常/中断、跳转执行,都是硬件实现的。
我们只需要在向量表中,把处理函数的地址填进去就可以了。
硬件承包了大部分的工作。
M3/M4的向量表中,存放的是函数地址。
2.3 cortex A7
参考资料:ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
实际上,以前的S3C2440属于ARM9处理器,它的异常/中断处理流程给cortex A7是一样的。
2.3.1 A7的向量表
A7的向量表中,放置的是某类异常的跳转指令。
比如发生Reset
异常时,CPU就会从向量表里找到第0项,得到b reset
指令,执行后就跳转到reset函数。
比如发生任何的中断时,CPU就会从向量表里找到第6项,得到ldr pc, _irq
指令,执行后就跳转到_irq函数。
- 跳转之前,硬件只会保存CPSR寄存器
- 跳转之后,软件要保存现场
- 函数执行完毕,返回之前,软件恢复现场
编译程序时,我们会先设置好异常向量表,程序开始运行,当运行到CMP指令时发生中断,CPU终止BLEQ指令的运行,会先去处理中断(irq):CPU会切换进入irq模式(若发生异常则进入各类异常对应的模式),接着CPU去异常向量表中得到一条指令并去执行该指令(注意与M3/M4的区别:这里是指令,而M3/M4是函数地址),该指令会一条跳转指令,会跳转去执行某个函数,在函数中首先会保存现场,接着分辨中断源并去处理中断,最后是恢复现场。
对于A7来说,硬件上并没有做太多事情,硬件上只帮我们切换CPU模式,跳转过来执行异常向量表中的某条指令,剩下的保存现场、分辨中断源以及恢复现场都是软件做的。
_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
2.3.2 A7的异常/中断处理流程
发生异常/中断时,硬件上实现了这些事情:
-
CPU切换到对应的异常模式,比如IRQ模式、未定义模式、SVC模式
-
保存被中断时的CPSR到SPSR
- CPSR:current program status register,当前程序状态寄存器
- SRSR:saved program status register,保存的程序状态寄存器
-
跳到这个异常的入口地址去,执行指令,这通常是一条跳转指令
软件要做的事情就比较多了:
- 保存现场
- 分辨异常/中断
- 调用对应的处理函数
- 恢复现场
A7的向量表中,存放的是跳转指令。
三、异常处理深入分析_保存现场
3.1 回顾处理流程
CPU每执行完一条指令都会检查有无中断/异常产生,发现有中断/异常产生,开始处理:
- 保存现场
- 分辨异常/中断,调用对应的异常/中断处理函数
- 恢复现场
对于不用的处理器,具体的处理工作有差别:
- 保存现场:cortex M3/M4里是硬件完成,cortex A7等是软件实现
- 分辨异常/中断:cortex M3/M4里是硬件完成,cortex A7等是软件实现
- 调用处理函数:cortex M3/M4里是硬件来调用,cortex A7等是软件自己去调用
- 恢复现场:cortex M3/M4里是软件触发、硬件实现,cortex A7等是软件实现
不管是硬件还是软件实现,第一步都是保存现场。
3.2 为什么要保存现场
任何程序,最终都会转换为机器码,上述C代码可以转换为右边的汇编指令。
对于这4条指令,它们可能随时被异常打断,怎么保证异常处理完后,被打断的程序还能正确运行?
-
这4条指令涉及R0、R1寄存器,程序被打断时、恢复运行时,R0、R1要保持不变
-
执行完第3条指令时,比较结果保存在程序状态寄存器里,程序被打断时、恢复运行时,程序状态寄存器保持不变
-
这4条指令,读取a、b内存,程序被打断时、恢复运行时,a、b内存保持不变
内存保持不变,这很容易实现,程序不越界就可以。
所以,关键在于R0、R1、程序状态寄存器要保持不变(当然不止这些寄存器):
- 在处理异常前,把这些寄存器保存在栈中,这称为保存现场
- 在处理完异常后,从栈中恢复这些寄存器,这称为恢复现场
3.3 保存现场
ARM处理器中有这些寄存器:
在arm中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)。
约定R0-R15寄存器的用途:
-
R0-R3
调用者和被调用者之间传参数
-
R4-R11
函数可能被使用,所以在函数的入口保存它们,在函数的出口恢复它们。
还有一个程序状态寄存器,对于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
3.4 对于M3/M4
参考资料:DDI0403E_B_armv7m_arm.pdf
、ARM Cortex-M3与Cortex-M4权威指南.pdf
、PM0056.pdf
2.4.1 硬件保存现场
3.4.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
,截图如下:
以前我们调用C函数时,会设置LR=返回地址,如果这样设置,它确实可以在执行完该函数后重新回到原位置执行,但是它无法把保存在栈中的寄存器恢复出来,此时对于M3/M4会进行一个操作:它把LR设置为一个特殊值,像往常一样去调用中异常/中断处理函数,当函数执行完毕后,会跳到LR指示的地方尝试去返回,此时CPU会发现LR是一个特殊值,于是会触发恢复现场,则会把栈中的值恢复到寄存器中,比如把返回地址恢复到PC寄存器中。
补充2个知识点:
-
操作模式:M3/M4有两个操作模式
- 处理模式:执行中断服务程序等异常处理时,处于处理模式
- 线程模式:执行普通应用程序代码时,处于线程模式
-
M3/M4有连个SP寄存器:SP_process、SP_main
- 有些RTOS在运行用户程序时会使用SP_process,默认使用SP_main。
3.5 对于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
当前程序正处于User(用户)或Sys(系统)模式下,此时正在执行第一条xxxx指令,执行完该指令后产生中断/异常,于是在硬件上会发生几件事:1.CPU强制进入IRQ模式,所谓IRQ模式就是以后程序使用SP时,用的就是IRQ模式里面的SP_IRQ的栈;2.当前程序处于User或Sys模式运行,那么就是用的对应下面程序状态寄存器(PSR),里面保存有比较的结果(前面的比较例子),此时该寄存器也会强制保存在IRQ模式下的SPSR_IRQ;3、跳转到IRQ入口执行异常向量表中的某条指令,比如跳过去PC执行_iqr函数。这部分主要是硬件做的事,其中主要做的就是把程序状态寄存器保存起来。
对于软件部分需要做以下几件事:1.保存现场。在前面可以的图可以看到,对于A7需要保存R0-R3,R12、LR寄存器,将以上几个寄存器保存进SP_IRQ中,图中还有PSR寄存器,但其已经被硬件保存起来;2.分辨、处理异常/中断;3.恢复现场。