进入异常中断处理
ARM处理器发生异常中断,则ARM处理器进入如下异常中断自动处理过程(假设发生的异常中断对应的模式为mode):
- 将当前程序状态寄存器CPSR的值保存到SPSR_mode中;
- 将CPSR中的模式位设置成mode模式,将CPSR中的bit7(I)设置为1,禁止IRQ中断,如果是FIQ中断,则再将CPSR中的bit6(F)设置为1,禁止FIQ中断;
- 将返回地址传给lr_mode;
- 将该异常中断的向量地址传给程序计数器pc,从而进入异常中断处理程序。
退出异常中断处理
当要从异常中断处理程序中返回时,要做以下两步操作(假设发生的异常中断对应的模式为mode):
- 将保存在SPSR_mode中的值恢复到当前程序状态寄存器CPSR中;
- 返回到发生异常中断的指令的下一条指令处执行,也就是将lr_mode寄存器的值适当地返回到程序计数器pc中。
但程序员只需做好上述第二步即可,第一步在完成第二步的同时由处理器自动完成,所以我们下面讲解从各种异常中断处理返回的编程接口。
退出复位异常中断处理(Reset)
复位异常中断处理程序不需要返回,所以不需要这个接口。
退出未定义指令异常中断处理(Undefined Instruction)
未定义指令异常中断由当前执行的指令自身产生,当未定义指令异常中断产生时,程序计数器pc的值还未更新,它指向当前指令后面第2条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置),当未定义指令异常中断发生时,处理器将值(pc-4)保存到lr_und中,此时(pc-4)指向当前指令的下一条指令,所以从未定义指令异常中断返回可以通过如下指令来实现:
mov pc, lr
该指令将寄存器lr_mode中的值到程序计数器pc中,实现程序返回,同时将SPSR_mode寄存器中的值到当前程序状态寄存器CPSR中。
如果要在异常中断处理中使用数据栈,那么可以在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场,编程如下:
stmfd sp!, {register_list, lr} ;保存被中断程序的执行现场
; . . .
ldmfd sp!, {register_list, pc}^ ;恢复被中断程序的执行现场
上面的register_list,是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_mode寄存器中的值到当前程序状态寄存器CPSR中。
退出软中断指令(SWI)异常中断处理(Undefined Instruction)
SWI异常中断和未定义异常中断指令一样,也是由当前执行的指令自身产生,当SWI指令执行时,pc的值还未更新,它指向当前指令后面第2条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置),当未定义指令异常中断发生时,处理器将值(pc-4)保存到lr_svc中,此时(pc-4)指向当前指令的下一条指令,所以从SWI异常中断处理返回的实现方法与从未定义指令异常中断处理返回一样:
mov pc, lr
使用数据栈的方法与未定义指令异常中断处理中的方法也一样:
stmfd sp!, {register_list, lr} ;保存被中断程序的执行现场
; . . .
ldmfd sp!, {register_list, pc}^ ;恢复被中断程序的执行现场
退出指令预取中止异常中断处理(Prefetch Abort)
在指令预取时,如果目标地址是非法的,该指令被标记成有问题的指令,这时,流水线上该指令之前的指令继续执行,当执行到该被标记成有问题的指令时,处理器产生指令预取中止异常中断。发生指令预取异常中断时,程序要返回到该有问题的指令处,重新读取并执行该指令,因此指令预取中止异常中断应该返回到产生该指令预取中止异常中断的指令处,而不是当前指令的下一条指令。
指令预取中止异常中断由当前执行的指令自身产生,当指令预取中止异常中断发生时,程序计数器pc的值还未更新,它指向当前指令后面第2条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置)。此时处理器将值(pc-4)保存到lr_abt中,它指向当前指令的下一条指令,所以返回操作可以通过下面指令实现:
subs pc, lr, #4
该指令将lr中的值减4后传给程序计数器pc中,实现程序返回,同时将SPSR_abt寄存器的内容到当前程序状态寄存器CPSR中。
如果要在指令预取中止异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:
subs lr, lr, #4
stmfd sp!, {register_list, lr} ;保存被中断程序的执行现场
; . . .
ldmfd sp!, {register_list, pc}^ ;恢复被中断程序的执行现场
上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_abt寄存器中的值到当前程序状态寄存器CPSR中。
退出数据访问中止异常中断处理(Data Abort)
发生数据访问异常中断时,程序要返回到该有问题的指令处,重新访问该数据,因此数据访问异常中断应该返回到产生该数据访问中止异常中断的指令处,而不是当前指令的下一条指令。
数据访问异常中断由当前执行的指令自身产生,当数据访问异常中断发生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置)。此时处理器将值(pc-4)保存到lr_abt中,它指向当前指令后面第2条指令,所以返回操作可以通过下面指令实现:
subs pc, lr, #8
该指令将lr中的值减8后传给程序计数器pc中,实现程序返回,同时将SPSR_abt寄存器内容到当前程序状态寄存器CPSR中;
如果要在数据访问异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:
subs lr, lr, #8
stmfd sp!, {register_list, lr} ;保存被中断程序的执行现场;
; . . .
ldmfd sp!, {register_list, pc}^ ;恢复被中断程序的执行现场;
上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_abt寄存器中的值到当前程序状态寄存器CPSR中。
退出IRQ异常中断处理程序(IRQ)
通常处理器执行完当前指令后,查询IRQ中断引脚,并查看是否允许IRQ中断,如果某个中断引脚有效,并且系统允许该中断产生,处理器将产生IRQ异常中断,当IRQ异常中断产生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当IRQ异常中断产生时,处理器将值(pc-4)保存到IRQ异常模式下的寄存器lr_irq中,它指向当前指令之后的第2条指令,因此返回操作可以通过下面指令实现:
subs pc, lr, #4
该指令将lr中的值减4后传给程序计数器pc中,实现程序返回,同时将SPSR_irq寄存器的内容到当前程序状态寄存器CPSR中。
如果要在IRQ异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:
subs lr, lr, #4
stmfd sp!, {register_list, lr} ;保存被中断程序的执行现场
; . . .
ldmfd sp!, {register_list, pc}^ ;恢复被中断程序的执行现场
上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_irq寄存器中的值到当前程序状态寄存器CPSR中。
退出FIQ异常中断处理程序(FIQ)
与IRQ异常中断一样,处理器执行完当前指令后,查询FIQ中断引脚,并查看是否允许FIQ中断,如果中断引脚有效,并且系统允许该中断产生,处理器将产生FIQ异常中断,当FIQ异常中断产生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当FIQ异常中断产生时,处理器将值(pc-4)保存到IRQ异常模式下的寄存器lr_fiq中,它指向当前指令之后的第2条指令,因此返回操作可以通过下面指令实现:
subs pc, lr, #4
该指令将lr中的值减4后传给程序计数器pc中,实现程序返回,同时将SPSR_fiq寄存器的内容到当前程序状态寄存器CPSR中。
如果要在FIQ异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:
subs lr, lr, #4
stmfd sp!, {register_list, lr} ;保存被中断程序的执行现场
; . . .
ldmfd sp!, {register_list, pc}^ ;恢复被中断程序的执行现场
上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_fiq寄存器中的值到当前程序状态寄存器CPSR中。
补充:关于程序返回地址(PC)的取值
上文中提到,在进入异常处理之后,CPU会自动根据pc的值来设置lr的值(一般是减4),而对于不同各类的异常来说,这个值还不能直接用做异常的返回地址,可能还需要再减4或减8等等,这样做的原因是什么呢?
答案在于ARM处理器在处理指令时所使用的三级流水线机制。
CPU执行一条指令的过程可以分为三个步骤:取指令、翻译和执行。执行每一个步骤都需要一个指令周期的时间,所以完整地执行完一条指令实际上就需要3个周期。为了加快程序的运行,现代CPU都会采用多级流程线的技术。以三级流水线为例,一条专门负责取指,一条专门翻译,还有一条负责执行,三条流水线并行工作,每一条流水线在每一个周期内都不会空闲,所以平均来看,执行每条指令都只要一个周期的时间。
从二进制指令的角度来看,当前指令在执行的时候,下一条指令已经在被翻译,再下一条指令已经正在被读取。注意pc寄存器总是指向正在被读取的那条指令,而不是正在被执行的指令。如下图所示:
箭头方向是指令运行的方向,左侧是低地址,右侧是高地址,当A指令是正在运行的指令,pc寄存器现在正指向C指令的位置。
下面,分别以软中断异常和数据异常为例来解释一下上文中所讲的内容:
在软中断发生时,指令流水线的结构与上图完全一样。软中断是由正在执行的指令A触发的,它的任务已经完成,所以在中断处理结束之后,A指令不需要再被执行一次,应该直接执行B指令。而在进入中断处理程序之前,CPU已经自动将(pc-4)的值存入lr,这正是B指定的位置。所以在中断返回时,直接把lr的值赋给pc就行了。
在数据访问异常发生时,指令流水线的结构与上图不太一样,正在执行的指令仍然是A,pc已经更新,即指向了D指令。在中断处理结束时,数据的问题已经解决(可以访问),A指令还需要再重新执行一次,所以pc需要指向A指令处。而在进入中断处理程序之前,CPU已经自动将(pc-4)的值存入lr,这是C指令的位置,所以我们需要手动调整pc的位置,把它再减8,这才是A指令的位置。
上一篇:ARM体系的异常中断
下一篇:arm处理器异常处理swi
推荐阅读最新更新时间:2024-03-16 14:57