linux ARM64 异常

linux 的系统调用是通过指令陷入不同异常级别实现的。arm64 架构的 cpu 的异常级别结构如下:

linux ARM64 异常_第1张图片

在上图中,用户层运行在 EL0 也就是异常级别 0,Linux 内核运行在 EL1 也就是异常级别 1,安全可信操
作系统运行在异常级别 2:EL2,安全监控模块运行在异常级别 3:EL3。
上述提到的用户层和内核大家都熟悉,为了介绍异常级别的完整性,我们简单说一下这里的 EL2 和 EL3,
这两个异常级别实际为了实现 TEE(Trusted Execution Environment)即可信执行环境的一种硬件架构,在 ARM 上
称之为 TrustZone,TrustZone 是 ARM 针对消费电子设备设计的一种硬件架构,其目的是为消费电子产品构建
一个安全框架来抵御各种可能的攻击。TrustZone 在概念上将 SoC 的硬件和软件资源划分为安全(Secure World)
和非安全(Normal World)两个世界,所有需要保密的操作在安全世界执行(如指纹识别、密码处理、数据加解密、
安全认证等),其余操作在非安全世界执行(如用户操作系统、各种应用程序等),安全世界和非安全世界通过
一个名为 Monitor Mode 的模式进行转换。这里了解一下就行,针对于系统调用我们涉及不到这个。
介绍完异常级别,下面介绍一下在 ARM64 下,是如何陷入不同运行级别的:
1)  异常级别 0 使用 svc( Supervisor Call)指令陷入异常级别 1
2)  异常级别 1 使用 hvc(Hypervisor Call)指令陷入异常级别 2
3)  异常级别 2 使用 smc(Secure Monitor Call)指令陷入异常级别 3
系统调用就是运行在异常级别 0 的用户程序,通过使用 svc 指令陷入异常级别 1,来完成的陷入动作,之后
会根据异常向量表,执行异常向量服务程序。本文以 ARM64 为例来看异常向量表的配置,内核在
arch/arm64/kernel/entry.S 汇编代码中设置了异常向量表。
在介绍异常向量表之前,先介绍一下异常。在 ARM64 体系结构中,异常分为同步异常和异步异常。同步异
常是试图执行指令时生成的异常,或是作为指令的执行结果生成的异常。同步异常包括如下。
1)  系统调用。异常级别 0 使用 svc( Supervisor Call)指令陷入异常级别 1,异常级别 1 使用 hvc
(Hypervisor Call)指令陷入异常级别 2,异常级别 2 使用 smc(Secure Monitor Call)指令陷入异常级
别 3。
2)  数据中止,即访问数据时的页错误异常,虚拟地址没有映射到物理地址,或者没有写权限。
3)  指令中止,即取指令时的页错误异常,虚拟地址没有映射到物理地址,或者没有执行权限。
4)  栈指针或指令地址没有对齐。
5)  没有定义的指令。
6)  调试异常。
异步异常不是由正在执行的指令生成的,和正在执行的指令没有关联。异步异常包括以下。
1)  中断( normal priority interrupt, IRQ),即普通优先级的中断。
2)  快速中断( fast interrupt, FIQ),即高优先级的中断。

3)  系统错误( System Error, SError),是由硬件错误触发的异常,例如最常见的是把脏数据从缓存行写
回内存时触发异步的数据中止异常。

/*
* Exception vectors.
*/
.pushsection ".entry.text", "ax"
.align 11
ENTRY(vectors)
kernel_ventry 1, sync_invalid //异常级别 1 生成的同步异常,使用栈指针寄存器 SP_EL0
kernel_ventry 1, irq_invalid //异常级别 1 生成的中断,使用栈指针寄存器 SP_EL0
kernel_ventry 1, fiq_invalid //异常级别 1 生成的快速中断,使用栈指针寄存器 SP_EL0
kernel_ventry 1, error_invalid //异常级别 1 生成的系统错误,使用栈指针寄存器 SP_EL0
kernel_ventry 1, sync //异常级别 1 生成的同步异常,使用栈指针寄存器 SP_EL1
kernel_ventry 1, irq //异常级别 1 生成的中断,使用栈指针寄存器 SP_EL1
kernel_ventry 1, fiq_invalid //异常级别 1 生成的快速中断,使用栈指针寄存器 SP_EL1
kernel_ventry 1, error_invalid //异常级别 1 生成的系统错误,使用栈指针寄存器 SP_EL1
kernel_ventry 0, sync //64 位应用程序在异常级别 0 生成的同步异常
kernel_ventry 0, irq // 64 位应用程序在异常级别 0 生成的中断
kernel_ventry 0, fiq_invalid // 64 位应用程序在异常级别 0 生成的快速中断
kernel_ventry 0, error_invalid //64 位应用程序在异常级别 0 生成的系统错误
#ifdef CONFIG_COMPAT
kernel_ventry 0, sync_compat, 32 //32 位应用程序在异常级别 0 生成的同步异常
kernel_ventry 0, irq_compat, 32 // 32 位应用程序在异常级别 0 生成的中断
kernel_ventry 0, fiq_invalid_compat, 32 // 32 位应用程序在异常级别 0 生成的快速中断
kernel_ventry 0, error_invalid_compat, 32 // 32 位应用程序在异常级别 0 生成的系统错误
#else
kernel_ventry 0, sync_invalid, 32 //32 位应用程序在异常级别 0 生成的同步异常
kernel_ventry 0, irq_invalid, 32 // 32 位应用程序在异常级别 0 生成的中断
kernel_ventry 0, fiq_invalid, 32 // 32 位应用程序在异常级别 0 生成的快速中断
kernel_ventry 0, error_invalid, 32 // 32 位应用程序在异常级别 0 生成的系统错误
#endif
END(vectors)

上面的代码进一步展开,就是设置不同 mode 下的异常向量表,异常可以分为 4 组,每组异常有 4 个,所以
这里一共会设置 16 个 entry。4 组异常分别对应 4 种情况下发生异常时的处理。以下是 4 个异常向量 entry 的含
义:
1)  同步异常
2)  中断
3)  快速中断
4)  系统错误

kernel_ventry 是一个宏,参数是跳转标号,即异常处理程序的标号,宏的定义如下(/arch/arm64/kernel/entry.S):
.macro kernel_ventry, el, label, regsize = 64
.align 7
sub sp, sp, #S_FRAME_SIZE //将 sp 预留一个 fram_size, 这个 size 就是 struct pt_regs 的
大小
#ifdef CONFIG_VMAP_STACK
//....这里省略掉检查栈溢出的代码
#endif
b el\()\el\()_\label // 跳转到对应级别的异常处理函数
.endm
“ .align 7”表示把下一条指令的地址对齐到 2^7,即对齐到 128; 对于向量表 vectors 中的 kernel_entry 0, sync,
则 b el\()\el\()_\label 跳转到 el0_sync 函数。 其中 0 表示的是从哪个异常模式产生的,比如是 user->kernel 就是
0., kernel->kernel 就是 1。
实现的函数有 el1_sync,el1_irq,el0_sync,el0_irq,el1_sync_compat,el1_irq_compat 其余都是 invalid。从
异常级别 1 的异常向量表可以看出如下内容。
1)  有些异常向量的跳转标号带有“invalid”,说明内核不支持这些异常,例如内核不支持 ARM64 处
理器的快速中断。
2)  对于内核模式(异常级别 1)生成的异常, Linux 内核选择使用异常级别 1 的栈指针寄存器。
3)  对于内核模式(异常级别 1)生成的同步异常,入口是 el1_sync。
4)  如果处理器处在内核模式(异常级别 1),中断的入口是 el1_irq。
5)  对于 64 位应用程序在用户模式(异常级别 0)下生成的同步异常,入口是 el0_sync。
6)  如果处理器正在用户模式(异常级别 0)下执行 64 位应用程序,中断的入口是 el0_irq。
7)  对于 32 位应用程序在用户模式(异常级别 0)下生成的同步异常,入口是 el0_sync_compat。
8)  如果处理器正在用户模式(异常级别 0)下执行 32 位应用程序,中断的入口是 el0_irq_compat。
前面设置了异常向量表,我们来进一步查看 SVC mode 的处理。当系统调用时 CPU 会切换到 SVC mode,并
跳转到对应的地址去运行。kernel 中会配置两个 SVC Handler,分别对应这 SVC_32/SVC_64 两种 mode,32bit
程序和 64bit 程序执行系统调用会跳转到两个不同的 handler 去执行。

你可能感兴趣的:(linux,驱动开发,瑞芯微平台,linux,驱动开发,c语言)