linux ARM64 异常

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

在上图中,用户层运行在 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。

  1. 数据中止,即访问数据时的页错误异常,虚拟地址没有映射到物理地址,或者没有写权限。

  2. 指令中止,即取指令时的页错误异常,虚拟地址没有映射到物理地址,或者没有执行权限。

  3. 栈指针或指令地址没有对齐。

  4. 没有定义的指令。

  5. 调试异常。

异步异常不是由正在执行的指令生成的,和正在执行的指令没有关联。异步异常包括以下。

  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 处

理器的快速中断。

  1. 对于内核模式(异常级别 1)生成的异常, Linux 内核选择使用异常级别 1 的栈指针寄存器。

  2. 对于内核模式(异常级别 1)生成的同步异常,入口是 el1_sync。

  3. 如果处理器处在内核模式(异常级别 1),中断的入口是 el1_irq。

  4. 对于 64 位应用程序在用户模式(异常级别 0)下生成的同步异常,入口是 el0_sync。

  5. 如果处理器正在用户模式(异常级别 0)下执行 64 位应用程序,中断的入口是 el0_irq。

  6. 对于 32 位应用程序在用户模式(异常级别 0)下生成的同步异常,入口是 el0_sync_compat。

  7. 如果处理器正在用户模式(异常级别 0)下执行 32 位应用程序,中断的入口是 el0_irq_compat。

前面设置了异常向量表,我们来进一步查看 SVC mode 的处理。当系统调用时 CPU 会切换到 SVC mode,并

跳转到对应的地址去运行。kernel 中会配置两个 SVC Handler,分别对应这 SVC_32/SVC_64 两种 mode,32bit

程序和 64bit 程序执行系统调用会跳转到两个不同的 handler 去执行。

相关推荐
9毫米的幻想31 分钟前
【Linux系统】—— 冯诺依曼体系结构与操作系统初理解
linux·运维·服务器·c语言·c++
leoufung2 小时前
vim 多个关键字高亮插件介绍
linux·编辑器·vim
USER_A0012 小时前
【C语言】第五期——函数
c语言
Nerd Nirvana5 小时前
软考—系统架构设计(案例 | 论文)
linux·系统架构·软件工程·软考·计算机基础
勤奋的凯尔森同学6 小时前
webmin配置终端显示样式,模仿UbuntuDesktop终端
linux·运维·服务器·ubuntu·webmin
李白同学8 小时前
【C语言】结构体内存对齐问题
c语言·开发语言
楼台的春风9 小时前
【MCU驱动开发概述】
c语言·驱动开发·单片机·嵌入式硬件·mcu·自动驾驶·嵌入式
打不了嗝 ᥬ᭄10 小时前
Linux的权限
linux
落幕10 小时前
C语言-进程
linux·运维·服务器
深度Linux11 小时前
C++程序员内功修炼——Linux C/C++编程技术汇总
linux·项目实战·c/c++