赞
踩
请阅读【ARMv8/v9 ARM64 System Exception】
ARM芯片手册通常使用 ELn 来表示第 n个异常等级。
在 ARMv8 架构的AArch64时,每种 EL 都具有独立的16个Entires, 这16
个 Entires分为四类异常:
IRQ
FIQ
SError
Synchronous
根据触发一种异常时是否会产生EL
级别的迁移和使用产生迁移时使用的是AArch64还是AArch32的指令集又具有四种不同的区域。
当在EL0
产生异常而未产生EL
的迁移时,使用的是SP0
保存的栈空间地址。
而在除EL0
之外的ELn
产生异常而未导致EL
的迁移时使用的是对应的SPn
保存的栈空间地址。
在AArch64中,EL
的迁移只能是从低EL向高EL
进行迁移,如果使用AArch64
指令集和AArch32
指令集时产生了异常并且也发生了EL的迁移时,则使用的是将迁移到的EL对应的SPn
。
异常主要是分成2大类,一类是同步,另外一类是异步。
data abort
、prefetch instruction abort
、SP未对齐异常,debug exception
等等。SVC/HVC/SMC
指令,这些指令的使命就是产生异常,改变执行权限。同步异常就是说中了指令的圈套,这样的,这些异常的发生是比较精准的。
而异步异常是和运行的指令没有关系,因为它是被中圈套。在ARMv7的那些异常,比如 data abort等,就符合这里的同步异常,而IRQ 和 FIQ 中断就符合异步异常。
先来说说什么是非精确异常?:在多发射乱序执行的流水线 CPU 上,从指令进入流水线到异常事件的发生,期间要经过若干流水级,此时 PC 的值已指向其后的某条指令,在实现非精确异常的 CPU 上就把此时的 PC 值作为引起异常指令的所在,也就是记录异常指令的PC并非真正的引起异常的指令所在,而是其后面的某条指令所在。
精确异常(precise exception),也就是记录异常指令的PC并非真正的引起异常的指令所在,而是其后面的某条指令所在。实现精确异常的 CPU,在最后指令提交时 (commit) 按指令流的顺序提交,异常的抛出也在该指令提交时,这样就能精确计算出引起异常的指令相对于当前 PC 的偏移,从而保证精确异常。不管是何类异常,记录异常指令的PC之前的所有指令都会被执行完成 (commit),之后的指令不会被执行。
在 AArch64中,除了SError interrupt 这种 exception,其他的 exception 都是 precise exception。
进入异常时会将 PSTATE
,即PE状态放在异常等级对应 的 SPSR_ELn
,
SP位: 栈指针寄存器选择位,对于Aarch64,栈指针寄存器有四个,EL0/LE1/EL2/EL3,但是SP位只有1bit
, 只能指示使用SP_EL0
还是SP_ELx
,所以就需求配合其他域来使用,这时就出现了SP_ELx
域。
对于EL1/EL2/EL3三个栈指针是怎么选择的?当异常发生时,现根据PSTATE
中的SP
位选决定使用是否使用SP_EL0
,然后再根据PSTATE
中的 EL 2 bit
决定到对应的异常等级的栈指针SP_ELx
(x>0)。
一般异常等级里执行程序时,都会再将SP位清零,也就是继续用SP_EL0
为什么不直接用SP_ELx
呢?
如果当ELx的异常发生了,然后用上了栈指针寄存器SP_ELx
,然后就开始执行处理的指令流,然后正在处理过程中,又发生了一个同级的异常。栈指针又立马选到了对应的异常等级的栈指针,这就是为何EL0的SP栈指针寄存器单独用了一个标志位来表示。
AIF 位: 分别为 SError
、IRQ
、FIQ
屏蔽位,置位后对应中断不能发生。
在AArch64中,调用ERET从一个异常返回时,会将SPSR_ELn
恢复到 PSTATE
中。
SPSR_ELn
寄存器里, 如果当前的异常发生在EL1
,则将 PSTATE
的状态保存到SPSR_EL1
中;ELR_ELn
寄存器里,n 对应异常等级;PSTATE
寄存器里的 D,A,I,F 域都设置为1,关掉其他异常,以防止处理异常时被打断了;ESR_ELn
寄存器的值找到对应的原因;ELR_ELn
从取出地址放到 PC、从 SPSR_ELn
取出状态放到 PSTATE
。AArch64 state异常处理流程 | 说明 |
---|---|
1、保存 PSTATE 数据到SPSR_ELx ,(x = 1,2,3) | 异常返回时需要从SPSR_ELx 中恢复 PSTATE |
2、保存异常进入地址到 ELR_ELx ,同步异常(und/abt等)是当前地址,而异步异常(irq/fiq等)是下一条指令地址 | 64位架构 LR 和 ELR 是独立分开的,这点和 32 位架构有所差别 |
3、保存异常原因信息到ESR_ELx | ESR_ELx.EC 代表 Exception Class,关注这个bit |
4、PE 根据目标 EL 的异常向量表中定义的异常地址强制跳转到异常处理程序 | 跳转到哪个 EL 使用哪个向量偏移地址又路由关系决定 |
5、堆栈指针 SP 的使用由目标 EL 决定 | (SPSR_ELx.M[0] == 1) ? h(ELx): t(EL0) |
在 ARMv7 的异常向量表,每个表项只有 4 个字节,只能存放一条跳转指令,但 ARMv8 里升级了,每个表项需要 128个字节,这样可以存放 32 条指令。ARMv8 指令集里一条指令的位宽是 32bit 的,而不是64bit。
Q1: 异常向量表里要包含 4 组,每一组包含4个表项。这怎么理解?
A:ARMv8 有 4 个异常级别,每一个异常级别对应一个 VBAR(Vector Base Address Register) 寄存器(VABR_EL1
, VABR_EL2
,VABR_EL3
),用来指向异常向量表的基地址,每一个异常向量表的大小为128个字节,也即可以存放32条指令;
同时每一个异常向量表会分为4组,每一组包含4 种异常,如下表 1-1所示。
Q2:current EL with SP_EL0是啥意思呢?
A:当系统运行在EL1的时候,然后使用EL0
的SP
,貌似还没有这种场景。
current EL with SP_EL1,这是说系统运行在EL1里,SP
也是使用EL1,那就是在内核态里发生了异常。
Q3:Current Exeception level 是什么意思?
A:比如当前系统只运行Linux内核不包括虚拟化或者安全特性,那最高的EL等级是EL1
,那么它就一定有EL0
。所以上面说的Current EL
就是说的当前系统最高的EL
等级。
如果没有使用 secure monitor 状态和 hvc 状态时,OS 运行在内核态时(EL1
)发生了 IRQ 中断,这时候应该跳转到异常表里的0x280处。
Q4:如果 OS 运行在用户态并且正在执行32bit的app,发生了 IRQ 中断,它应该跳转到哪里呢?
A:应该这时应该跳转到0x0x680
处。
ARMv8 有4张向量表,每张向量表有4中异常:同步异常、irq异常、fiq异常、系统错误异常,而4张表分别对应:
SP_EL0
;SP_ELn
;Q:关于异常向量表,还有一个问题需要考虑,那就是异常向量表放在哪里?
A:ARM v8里提供了VBAR
(Vector Base address register), ARMv8
中,EL1,EL2,EL3都具有独立的 VBAR
寄存器,该寄存器就是用于存放各VBAR_ELn
的异常向量表的基地址。当发生异常时,如果该异常产生了EL
的迁移,那么完成迁移操作之后会到迁移到的 EL
中的VBAR
寄存器中找到向量表的基地址,然后命中对应的 handler.
Linux内核代码(以runninglinux_4.0
为例)。代码路径是在arch/arm64/kernel/entry.S
文件里。
.align 11 ENTRY(vectors) /* 第1处 */ kernel_ventry 1, sync_invalid // Synchronous EL1t kernel_ventry 1, irq_invalid // IRQ EL1t kernel_ventry 1, fiq_invalid // FIQ EL1t kernel_ventry 1, error_invalid // Error EL1t /* 第 2 处 */ kernel_ventry 1, sync // Synchronous EL1h kernel_ventry 1, irq // IRQ EL1h kernel_ventry 1, fiq_invalid // FIQ EL1h kernel_ventry 1, error_invalid // Error EL1h /* 第 3 处 */ kernel_ventry 0, sync // Synchronous 64-bit EL0 kernel_ventry 0, irq // IRQ 64-bit EL0 kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0 kernel_ventry 0, error_invalid // Error 64-bit EL0 #ifdef CONFIG_COMPAT kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0 kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0 kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0 kernel_ventry 0, error_invalid_compat, 32 // Error 32-bit EL0 #else /*第 4 处*/ kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0 kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0 kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0 kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0 #endif END(vectors)
kernel_ventry
是宏,翻译后的函数名是(以第2处为例):
/* * Bad Abort numbers *----------------- */ #define BAD_SYNC 0 #define BAD_IRQ 1 #define BAD_FIQ 2 #define BAD_ERROR 3 .macro kernel_ventry, el, label, regsize = 64 .align 7 .Lventry_start\@: .if \el == 0 /* * This must be the first instruction of the EL0 vector entries. It is * skipped by the trampoline vectors, to trigger the cleanup. */ b .Lskip_tramp_vectors_cleanup\@ .if \regsize == 64 mrs x30, tpidrro_el0 msr tpidrro_el0, xzr .else mov x30, xzr .endif .Lskip_tramp_vectors_cleanup\@: .endif sub sp, sp, #S_FRAME_SIZE #ifdef CONFIG_VMAP_STACK /* * Test whether the SP has overflowed, without corrupting a GPR. * Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT) * should always be zero. */ add sp, sp, x0 // sp' = sp + x0 sub x0, sp, x0 // x0' = sp' - x0 = (sp + x0) - x0 = sp tbnz x0, #THREAD_SHIFT, 0f sub x0, sp, x0 // x0'' = sp' - x0' = (sp + x0) - sp = x0 sub sp, sp, x0 // sp'' = sp' - x0 = (sp + x0) - x0 = sp b el\()\el\()_\label
上面汇编代码 b el\()\el\()_\label
会转化成下面内容:
el1_sync
el1_irq
el1_fiq_invalid
el1_error_invalid
上图中 4 处对应EL1表项的 4 组。上文中说过每个表项是128个字节吗,但是从上图中并卡不出来,注意上面“ENTRY(vectors)
”, 这里使用的是align伪指令,它使用align 7
,表示按照2的7次方来对齐,2的7次方是128。关于ARM伪指令的详细介绍见 【ARM64 常见汇编指令学习系列文章】
\* Vectory entry *\
.macro ventry label
.align 7
b \label
.endm
上篇文章:ARMv8/v9 异常模型系列 3 - Process State介绍
下篇文章:ARMv8/v9 异常模型系列 5 - IRQ 异常处理流程
推荐阅读:
http://blog.chinaunix.net/uid-69947851-id-5830546.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。