- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
越往后,交叉的越多,大多都绕不开 ARMv8 的异常处理,所以必须得先了解了解 ARMv8 的异常处理流程 。
先说一下术语,从手册中的用词来看,在 x86 平台,一般将异常和中断统称为中断,在 ARM 平台,一般将中断和异常统称为异常 。
异常的流程,可以分为 3 个阶段,“设备”产生异常信号,中断控制器过滤转发异常,OS 处理异常。设备产生异常的部分我们不讨论,后两个阶段需要仔细说道说道,先来看 ARM 的中断控制器.
ARM 使用的中断控制叫做 Generic Interrupt Controller,中断控制器主要作用就是转发设备的中断信号,因为外设可能很多,中断信号也很多,不可能每个外设都连一根线与 cpu 相连,所以要有中断控制器来作为中转站 。
gic 发展到现在有 4 个版本:
上述是手册给出的每个 gic 版本特性,以及对应的 ARM 芯片型号,目前手机端应该用的是 GICv3 居多,qemu 默认使用的似乎是 GICv2.
gicv1 只支持 8 个 PE,PE 是 arm 架构下定义的抽象机器,processing element,可以简单理解为最多支持 8 个 cpu。最多支持 1020 个中断,支持 2 种安全状态,arm 架构一直有个 安全模式,这个我们暂且不提 。
从 gicv2 开始便在硬件级别支持了虚拟化,有了这个扩展,可以向虚拟机注入中断,也就是向虚拟机发送中断信号 。
从 gicv3 开始,gic 的架构有了较大的变化,具体的见手册,从 gicv4 开始支持直接投递中断,具体含义后面详细说明,这里只是过过眼,本篇讲述 gicv2 的基本知识 。
后续代码讲述状态到底是如何转换的 。
edge 边沿触发,level 电平触发 。
gicv2 的总体架构图如上所示,主要分为两部分:
有关 Distributor 的寄存器都是以 GICD_xxx 开头 。
主要寄存器如上所示,具体含义见手册,这里我就不做这个翻译工作了 。
Interface 的寄存器都是以 GICC_xxx 开头:
同样的就不做翻译工作了,重点寄存器我后面都会在代码中提及 。
TODO 。
NOTE,以下大部分内容来自 ARM 手册 https://developer.arm.com/documentation/102412/0103,感兴趣的建议直接看原手册,更详尽.
ARMv8 相较于 ARMv7,在整体架构上有了较大的变化,ARMv8 实现了 4 个异常等级 EL0~EL3,EL0 运行用户态程序,EL1 运行 Guest OS,EL2 运行 hypervisor,EL3 运行 secure monitor 程序.
EL0、EL1 是 ARM 芯片必须实现的异常级别,EL2、EL3 异常级别是可选的 。
另外还有个安全世界,EL1、EL2 可以通过 smc 指令切换到安全世界,安全世界也运行了一个 OS,叫做 TrustOS,TrustOS 之上也运行了一些应用,通常称作为安全应用,比如手机中常见的支付操作,与安全强相关的操作都会调用到安全世界。可以这样简单理解,安全世界提供了一系列安全功能,需要使用 smc 系统调用来调用这些安全服务。安全世界的话题就此打住,后面有时间写个 TEE 系列(经典有时间) 。
ARMv8 支持 AArch32 和 AArch64 两种执行状态,不同的执行状态下使用的指令集和寄存器都是不同的(是同一个物理硬件,但是使用的寄存器的位数命名等有所不同) 。
执行状态是可以切换的,但只有在系统重置或者异常级别更改的时候才能更改执行状态。在异常级别更改的时候更改执行状态也有两条规则:
这两条规则就是说 64 位层次可以托管 32 位层次,反之则不行,比如说 64 位的内核上可以运行 32 位程序,而 32 位的内核只能运行 32 位的应用程序。一图以蔽之:
前面说过在 gic 的角度上来看,中断可以分为 SGI、SPI、PPI。现在从 CPU 角度来看,异常有哪些类型,主要分为两大类:Synchronous exceptions 和 Saynchronous exceptions,就是同步异常和异步异常。就是 x86 平台对异常和中断的分类,两个平台习惯叫法不同而已.
同步异常有以下几个特点:
同步异常又分为以下几种情况:
异步异常是在 CPU 外部产生的,所以与当前的指令流不同步。异步异常与当前正在执行的指令没有直接关联,通常是来自处理器外部的系统事件,异步异常通常又被称为中断,异步异常有以下几种情况:
这部分再过一遍异常流程涉及的一些寄存器 。
31 个 64bit 通用寄存器 x0~x31,x29 是 fp 保存上一个栈帧底部地址,x30 保存返回地址,minos 中 x18 存放当前线程地址 。
Register | Name | Description |
---|---|---|
Exception Link Register | ELR_ELx | Holds the address of the instruction which caused the exception |
Exception Syndrome Register | ESR_ELx | Includes information about the reasons for the exception |
Fault Address Register | FAR_ELx | Holds the virtual faulting address |
Hypervisor Configuration Register | HCR_ELx | Controls virtualization settings and trapping of exceptions to EL2 |
Secure Configuration Register | SCR_ELx | Controls Secure state and trapping of exceptions to EL3 |
System Control Register | SCTLR_ELx | Controls standard memory, system facilities, and provides status information for implemented functions |
Saved Program Status Register | SPSR_ELx | Holds the saved processor state when an exception is taken to this ELx |
Vector Base Address Register | VBAR_ELx | Holds the exception base address for any exception that is taken to ELx |
异常处理是硬件和软件一起完成的,对于硬件 CPU 需要完成的部分是保存最基本的现场,它会做以下的事情:
异常处理完成后,基本就是上述的逆操作,总体如下图所示:
上述描述有一个小问题,异常处理通常都是在更高级别或者同级别处理(EL0 不能处理异常),那这个更高级别指的是哪个级别,有多高?
如果是只实现了 EL0 和 EL1 两个级别的机器,那么就只能在 EL1 级别处理中断。如果 4 个级别都实现了,那么也有一些寄存器来配置异常的路由情况。比如说配置 HCR 寄存器可以将一些异常路由到 EL2 的 hypervisor,执行 EL2 的异常处理程序来处理异常,配置 SCR 寄存器可以将异常路由到 secure monitor。所以这个具体在哪一个级别处理异常都是可以配置的,通常有 hypervisor,就会配置 HCR,让异常路由到 EL2,EL2 可以自己处理,也可以再注入到虚拟机,让虚拟机的内核处理.
还记得初次见这张异常向量表的时候,当时是在奔叔的 Linux 书籍里面,那是一脸的懵逼,这里来详细解释一下.
首先回顾一下,异常入口确定方式 。
中断向量(Interrupt Vector)是计算机系统中用于处理中断的一种技术。它是一个包含各种中断处理程序入口地址的表格。当发生中断时,中断控制器通过查找中断向量表找到对应的中断处理程序地址,然后跳转到该地址执行相应的处理.
EL1~EL3 都有一个 VBAR_ELx 寄存器,里面存放着异常向量表的基地址,都有一个像上面的异常向量表。向量可以分为两大类,四种:
大部分应该还是挺好理解的,就是为啥有个 SP_EL0,正常情况下,就是处于哪个异常等级,就是用哪个等级的 SP。但是 ARM 提供了一种机制可以在 ELx 使用 SP_EL0:
什么情况下会在 ELx 上使用 SP_EL0 呢?
一般来说 EL0 用户态的栈比较大,在内核栈可能溢出的情况下,那么我们就可以使用 EL0 栈。对于栈溢出的情况,通常都要使用另外一个栈来处理。在信号处理中,对于栈溢出通常需要使用 sigaltstack 系统调用来设置另外一个栈来处理信号(原来的栈满了,当然不能执行函数处理信号了) 。
但是 Linux 内核现在都没使用这个特性,不过既然 ARM 提供了这个特性,那么可以用它来存放一些内核重要数据,比如说 Linux 内核常见的 get_current() 获取当前线程结构体指针:
static __always_inline struct task_struct *get_current(void)
{
unsigned long sp_el0;
asm ("mrs %0, sp_el0" : "=r" (sp_el0));
return (struct task_struct *)sp_el0;
}
#define current get_current()
可以看出,在内核态(EL1) 的时候,SP_EL0 里面存放的是当前线程结构体指针。这个 SP_EL0 在用户态的时候指向的是用户栈,那什么时候变成 task_struct 指针的,又是什么时候恢复的呢?可以猜到,多半是进入内核态以及返回用户态的时候做的这些操作,查找代码验证下:
// linux-6.6.23
// 进入内核态时:
.if \el == 0
clear_gp_regs
mrs x21, sp_el0
ldr_this_cpu tsk, __entry_task, x20
msr sp_el0, tsk
原先的 SP_EL0 和其他的通用寄存器都会保存到 SP_EL1,等到异常返回时,都会恢复 。
现代的异常处理基本都包括上述两个阶段:
处理完成之后,执行上述的逆操作,将保存在高特权级栈里面的信息恢复,之后通常紧接着一条 eret 指令,实现异常返回,异常返回就是将保存在 SPSR_ELx 寄存器中的状态字恢复到 PSTATE,将保存在 ELR 的返回地址恢复到 PC 。
最后此篇关于minos2.1中断虚拟化——ARMv8异常处理的文章就讲到这里了,如果你想了解更多关于minos2.1中断虚拟化——ARMv8异常处理的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
问题很简单:我正在寻找一种优雅的使用方式 CompletableFuture#exceptionally与 CompletableFuture#supplyAsync 一起.这是行不通的: priva
对于 Web 服务,我们通常使用 maven-jaxb2-plugin 生成 java bean,并在 Spring 中使用 JAXB2 编码。我想知道如何处理 WSDL/XSD 中声明的(SOAP-
这个问题已经有答案了: Array index out of bound behavior (10 个回答) 已关闭 8 年前。 我对下面的 C 代码感到好奇 int main(){
当在类的开头使用上下文和资源初始化 MediaPlayer 对象时,它会抛出 NullPointer 异常,但是当在类的开头声明它时(因此它是 null),然后以相同的方式初始化它在onCreate方
嘿 我尝试将 java 程序连接到 REST API。 使用相同的代码部分,我在 Java 6 中遇到了 Java 异常,并且在 Java 8 中运行良好。 环境相同: 信任 机器 unix 用户 代
我正在尝试使用 Flume 和 Hive 进行 Twitter 分析。为了从 twitter 获取推文,我在 flume.conf 文件中设置了所有必需的参数(consumerKey、consumer
我在 JavaFX 异常方面遇到一些问题。我的项目在我的 Eclipse 中运行,但现在我的 friend 也尝试访问该项目。我们已共享并直接保存到保管箱文件夹中。但他根本无法让它发挥作用。他在控制台
假设我使用 blur() 事件验证了电子邮件 ID,我正在这样做: $('#email').blur(function(){ //make ajax call , check if dupli
我这样做是为了从 C 代码调用非托管函数。 pCallback 是一个函数指针,因此在托管端是一个委托(delegate)。 [DllImport("MyDLL.dll")] public stati
为什么这段代码是正确的: try { } catch(ArrayOutOfBoundsException e) {} 这是错误的: try { } catch(IOException e) {} 这段
我遇到了以下问题:有导出函数的DLL。 代码示例如下:[动态链接库] __declspec(dllexport) int openDevice(int,void**) [应用] 开发者.h: __de
从其他线程,我知道我们不应该在析构函数中抛出异常!但是对于下面的例子,它确实有效。这是否意味着我们只能在一个实例的析构函数中抛出异常?我们应该如何理解这个代码示例! #include using n
为什么需要异常 引出 public static void main(String[
1. Java的异常机制 Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内
我是 Python 的新手,我对某种异常方法的实现有疑问。这是代码(缩写): class OurException(Exception): """User defined Exception"
我已经创建了以下模式来表示用户和一组线程之间的关联,这些线程按他们的最后一条消息排序(用户已经阅读了哪些线程,哪些没有): CREATE TABLE table(user_id bigint, mes
我正在使用 Python 编写一个简单的自动化脚本,它可能会在多个位置引发异常。在他们每个人中,我都想记录一条特定的消息并退出程序。为此,我在捕获异常并处理它(执行特定的日志记录操作等)后引发 Sys
谁能解释一下为什么这会导致错误: let xs = [| "Mary"; "Mungo"; "Midge" |] Array.iter printfn xs 虽然不是这样: Array.iter pr
在我使用 Play! 的网站上,我有一个管理部分。所有 Admin Controller 都有一个 @With 和一个 @Check 注释。 断开连接后,一切正常。连接后,每次加载页面(任何页面,无论
我尝试连接到 azure 表存储并添加一个对象。它在本地主机上工作得很好,但是在我使用的服务器上我得到以下异常及其内部异常: Exception of type 'Microsoft.Wind
我是一名优秀的程序员,十分优秀!