#创作者激励#【FFH】操作系统基础知识梳理(1)中断与异常 原创 精华
【本文正在参加2023年第一期优质创作者激励计划】
在持续探索开源鸿蒙操作系统内核的过程中有必要从LINUX,AOSP或其他小型操作系统中先深入分析汲取基础知识经验,进而对照分析学习进而更深入学习理论和进行实践。
接下来从操作系统的基础知识按照常规划分结构,从硬件基础结构,操作系统结构,内存管理,进程线程,操作系统调度,同步原语,文件系统与存储,设备管理,系统虚拟化,网络协议,安全,调试,几个部分来进行基础知识的梳理。深入了解操作系统的内核,补全基础知识。
操作系统运行机制(中断与异常)
中断和异常基础知识
操作系统中断和异常是计算机系统中重要的概念,它们可以帮助操作系统处理各种事件和错误
中断(Interrupt)
中断是指在计算机执行程序时,突然发生的一些事件,例如输入输出操作完成、定时器超时、硬件错误等等。当发生中断时,操作系统会停止当前进程的执行,并处理中断事件。处理完毕后,操作系统会回到原来的进程并继续执行。
- 外部硬件设备产生的信号如键盘按键响应,鼠标响应
- 异步:产生原因和当前的执行指令无关,如程序被磁盘的读写打断
异常(Execption)
异常是指在程序执行过程中出现的错误或非正常情况。例如,除以零、访问不存在的内存地址等等。当发生异常时,操作系统会停止当前进程的执行,并处理异常。处理完毕后,操作系统会终止当前进程并回收资源。
- 软件的程序执行而产生的事件
- 包括系统调用(System Call)-用户程序请求操作系统提供服务
- 同步:产生和当前执行或试图执行的指令相关的
在不同的处理器架构上定义有所不同实现方式也有所不同
主要在主流处理器架构AArch64和x86-64上进行探讨。
-
在中断处理方面,AArch64和x86-64的处理方式比较相似,都有中断向量表和中断处理程序。当中断发生时,处理器会根据中断向量表中的入口地址跳转到对应的中断处理程序。不过,在中断优先级方面,AArch64使用的是基于中断源编号的优先级,而x86-64使用的是基于中断向量号的优先级。
-
在异常处理方面,AArch64和x86-64的处理方式有一些区别。AArch64定义了3种不同的异常:同步异常、异步异常和中断。同步异常是由当前指令在执行过程中发生的异常,而异步异常则是由硬件或软件事件触发的异常。中断则是一种特殊的异常,它由外部设备触发,与异步异常的处理方式相似。在AArch64中,异常处理程序被称为异常向量表,其中包含了不同类型异常的入口地址。而在x86-64中,异常被称为中断,异常处理程序被称为中断处理程序,它们的处理方式和中断处理方式相同。
此外,AArch64和x86-64在异常处理的寄存器保存和恢复方面也有一些不同。在AArch64中,异常处理程序会保存通用寄存器
、程序状态寄存器
以及特定的系统控制寄存器
。而在x86-64中,中断处理程序会保存通用寄存器
、标志寄存器
、指令指针
以及特定的中断控制寄存器
。
AArch64
中断
- 重置 (RESET) -最高级别的异常,但是由芯片内部电器信号触发,(严格划分不属于外部中断)。
- 中断(Interrupt) -CPU外部信号触发,打断执行 ,如计时器中断,串口终端,空闲中断,
异常
- 中止(Abort) - 失败的指令获取或数据访问,访问到不可读地址
- 异常产生指令
- SVC : 用户程序 -》 操作系统
- HVC : 客户系统 -》 虚拟机管理器
- SMC :Normal -> Secure World
X86-64
中断(设备产生,异步)
- 可屏蔽 - 设备产生的信号,通过中断控制器与处理器项链,能够被暂时屏蔽
- 不可屏蔽:一些关键硬件的崩溃错误
异常
三大类分类 :
- 错误 (Fault)可恢复(缺页异常),段错误(不可恢复)
- 陷阱 (Trap)无需回复
- 中止 (Abort)严重错误,不可恢复,需要进行机器检查。
中断和异常的产生机制
中断
如图所示当发生异常 (如除以零错误) 时,CPU 会向操作系统异常处理程序发出信号,以便其接管并处理异常。操作系统异常处理程序随后确定异常的原因,并决定是否可以恢复。如果可以恢复,处理程序将尝试从异常中恢复。如果无法恢复,系统将崩溃.
当发生中断 (如设备请求) 时,CPU 会向中断处理程序发出信号,以便其接管并处理中断。中断处理程序随后确定中断的原因,并决定是系统调用还是设备中断。如果是系统调用,处理程序将执行请求的系统调用。如果是设备中断,处理程序将处理设备中断 (如通过读取设备数据)。
在ARM架构内核和X86架构内核当中,有如下所示的SOC连接方式
当中连线最多的部分是GIC
也就是(General interrupt controller)部分有多根总线连接到不同的设备上。
- 硬件层:最下层为硬件连接层,对应的是具体的外设与SoC的物理连接,中断信号是从外设到中断控制器,由中断控制器统一管理,再路由到处理器上;
- 硬件相关层:这个层包括两部分代码,一部分是架构相关的,比如ARM64处理器处理中断相关,另一部分是中断控制器的驱动代码;
- 通用层:这部分也可以认为是框架层,是硬件无关层,这部分代码在所有硬件平台上是通用的;
- 用户层:这部分也就是中断的使用者了,主要是各类设备驱动,通过中断相关接口来进行申请和注册,最终在外设触发中断时,进行相应的回调处理;
中断控制器GIC需要考虑的问题
- 如何指定不同中断的优先级
- 低优先级的终端处理中,出现高优先级的中断如何响应
- 嵌套中断
- 谁来处理中断
- 和中断控制器协同的软件怎么编写
在aarch64架构中,GIC支持不同的中断优先级,这样可以确保重要的中断可以优先处理。中断优先级由中断号和中断类型确定,每个中断都有一个唯一的中断号,它是一个32位的整数,同时,每个中断也有一个对应的中断类型,可以是中断或异常。
中断优先级是通过GIC中的中断控制器寄存器来实现的。GIC提供了四个中断优先级寄存器,分别为CPU Interface控制器寄存器的ICC_PMR、Distributor控制器寄存器的ICDIPR、ICDIPTR和ICDIPRn(其中n是从0开始的中断号)。这些寄存器用于设置中断优先级和掩码。ICC_PMR是CPU Interface控制器的一个寄存器,它用于指定中断的优先级阈值。当中断的优先级高于这个阈值时,中断将被立即处理,否则将被挂起。这个阈值可以是0到255之间的任何值。ICDIPR是Distributor控制器的一个寄存器,它用于设置每个中断的优先级。每个中断都有一个对应的ICDIPR寄存器,它是一个8位的寄存器,用于指定中断的优先级。值越大,优先级越高。ICDIPTR是Distributor控制器的一个寄存器,它用于将中断连接到处理器。每个中断都有一个对应的ICDIPTR寄存器,它是一个8位的寄存器,用于指定中断连接到哪个处理器。ICDIPRn是Distributor控制器的一组寄存器,用于设置和查询每个中断的优先级和掩码。每个中断都有一个对应的ICDIPRn寄存器,它是一个32位的寄存器,其中包含了中断的优先级和掩码信息。通过这些寄存器,我们可以灵活地配置中断的优先级,从而确保系统能够高效地处理各种中断信号。
除了中断控制器寄存器控制中断号其次还对中断进行了划分 ,VFIQ,VIRQ,IRQ ,FIQ,SError。IRQ是指普通中断,优先级低,处理缓慢,FIQ是快速中断,常常为可信任的中断源预留,SError是原因难以定位的异常,通常由异步中止Abort导致。,VIRQ和VFIQ是针对虚拟化的
ARM GIC V2.0中给出的GIC模型如下
在上图中可以看到中断路由功能的实现
以及GIC主要包含三个重要部分
Distributor
、CPU interfaces
和 Virtual CPU interfaces
。Virtual CPU interfaces
包含 Virtual interface control
和 Virtual CPU interface
图中Distributor部分的主要作用为检测中断源,控制中断源行为和将中断源分发到指定CPU的指定接口上。
具体功能包括
- 全局启用中断转发到
CPU
接口 - 开启或关闭每一个中断
- 为每个中断设置优先级
- 为每个中断设置目标处理器列表
- 设置每个外设中断触发方式(电平触发、边缘触发)
- 为每个中断设置组
- 将
SGI
转发到一个或多个处理器 - 每个中断状态可见
- 提供软件设置或清除外设中断的挂起状态的一种机制
在早期的中断控制器中并不是这种架构,在我们常规使用的Contex M2-M3系列芯片中如今仍然使用的是内嵌式中断向量控制器NVIC,在NVIC中会维护一张表,中断向量表是一个表,这个表里面存放的是中断向量。中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。这些中断服务程序(函数)在中断向量表中的位置是由半导体厂商定好的,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。
中断被划分为三种类型
SGI(Software Generated Interrupt) - 软件触发中断:通常用于多核间通讯,最多支持 16 个 SGI 中断,硬件中断号从 ID0~ID15。
PPI(Private Peripheral Interrupt) - 私有外设中断:是每个 CPU 私有的中断。最多支持 16 个 PPI 中断,硬件中断号从 ID16~ID31。可以处理指定核心的中断任务
SPI(Shared Peripheral Interrupt)- 公用外设中断:最多可以支持 988 个外设中断,硬件中断号从 ID32~ID1019,可以处理按键中断,串口中断等。
中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020个 ID 包含了 PPI、SPI 和 SGI,那么这三类中断是如何分配这 1020 个中断 ID 的呢?这 1020 个 ID 分配如下:
ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断 ,至于具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。比如 I.MX6U的总共使用了 128 个中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID, I.MX6U 的中断源共有 128+32=160个,这 128 个中断 ID 对应的中断在《I.MX6ULL 参考手册》的“3.2 Cortex A7 interrupts”小节NXP 官方 SDK中的文件 MCIMX6Y2C.h,在此文件中定义了一个枚举类型 IRQn_Type,此枚举类型就枚举出了 I.MX6U 的所有中断,代码如下所示:
GIC中断抢占
GIC中断控制器支持中断优先级抢占,一个高优先级中断可以抢占一个低优先级且处于active状态的中断,即GIC仲裁单元会记录和比较当前优先级最高的pending状态,然后去抢占当前中断,并且发送这个最高优先级的中断请求给CPU,CPU应答了高优先级中断,暂停低优先级中断服务,进而去处理高优先级中断。
GIC会将pending状态优先级最高的中断请求发送给CPU。
在这里我们就必须要重新回顾刚才提到的图片中的CPU interface部分的更详细的信息。
GIC
为每个 CPU
接口上每个受支持的中断维护一个状态机。下图显示了此状态机的实例,以及可能的状态转换。
在这个单元中包含了 Inactive ,Pending,Active and Pending以及Active四种状态,和操作系统进程的状态类似,我们重点关注转换过程而不是状态本身。
(1) 当GIC检测到一个中断发生时,会将该中断标记为pending状态(A1)。
(2) 对处于pending状态的中断,仲裁单元回确定目标CPU,将中断请求发送到这个CPU上。
(3) 对于每个CPU,仲裁单元会从众多pending状态的中断中选择一个优先级最高的中断,发送到目标CPU的CPU Interface模块上。
(4) CPU Interface会决定这个中断是否可以发送给CPU。如果该终端优先级满足要求,GIC会发生一个中断信号给该CPU。
(5) 当一个CPU进入中断异常后,会去读取GICC_IAR寄存器来响应该中断(一般是Linux内核的中断处理程序来读寄存器)。寄存器会返回硬件中断号(hardware interrupt ID),对于SGI中断来说是返回源CPU的ID。
总结整理为 添加新的挂起状态(A1,A2),删除挂起状态(B1,B2),挂起到激活状态(C),挂起到激活和挂起(D),删除激活状态。
从GIC角度看,GIC会发送高优先级中断请求给CPU。
但是目前CPU处于关中断状态,需要等低优先级中断处理完毕,直到发送EOI给GIC。
然后CPU才会响应pending状态中优先级最高的中断进行处理。
所以Linux下:
-
高优先级中断无法抢占正在执行的低优先级中断。
-
同处于pending状态的中断,优先响应高优先级中断进行处理。
对于ARM体系的处理器,通过判断CPSR寄存器中的I位或F位的值确定处理器是否通过启动中断确认过程来响应中断信号,处理抢占中断是,处理器必须保存并稍后恢复为刚刚活动的ISR的上下文,
优先级下降意味着中断的优先级不再影响 CPU
接口上的运行优先级,因此不会阻止中断抢占。在 GICv1
实现中,仅当中断停用时才会发生优先级下降,但在 GICv2
实现中,优先级下降和中断停用可以分开。这也就解决了刚刚提出的具有冲突可能的问题。
在虚拟机中如何支持IRQ和FIQ中断
该用例来自于GICV2.0的说明文档,指出,在CPU需要进行虚拟化时,GIC的响应方式。
安全软件分配:
对组 0 的安全中断,作为 FIQ 信号发送到处理器。
对组 1 的非安全中断,作为 IRQ 向处理器发出信号。
虚拟机管理程序:
使用 GIC 上的虚拟化扩展的功能实现虚拟分发服务器。此虚拟分发服务器可以将来自 GIC 的 IRQ 中断虚拟化为虚拟 IRQ 和虚拟 FIQ 中断,并将其路由到相应的虚拟机。
将物理 IRQ 路由到 Hyp 模式,以便虚拟分发服务器可以为其提供服务。当虚拟化方案使用基于虚拟化扩展(Virtualization Extensions,简称VE)的虚拟化方式时,物理中断控制器(Physical GIC,简称PGIC)会将物理IRQ路由到Hyp模式,Hypervisor进行处理。硬件会根据中断的优先级和路由信息将中断发送到正确的处理器模式中(如Hyp模式、Guest模式等)。
在虚拟机上运行的客户机操作系统将中断分配给组 0 或组 1,以将其分配为 FIQ 或 IRQ。对 GIC 分发服务器寄存器的访问将捕获到虚拟机管理程序,因此可以访问虚拟分发服务器。
虚拟 CPU 接口将这些中断作为虚拟 FIQ 或虚拟 IRQ 发出信号。此虚拟化受虚拟机管理程序控制,对来宾操作系统不可见。
当 GIC 向处理器发送 IRQ 信号时,中断将路由到 Hyp 模式。虚拟机管理程序确定中断是针对自身还是针对来宾操作系统。如果是针对来宾操作系统,它将确定:
哪个客户机操作系统必须处理中断。
该来宾操作系统是否已将中断配置为 FIQ 或 IRQ。
中断优先级,基于目标来宾操作系统的优先级配置。
综合而言可以理解为虚拟机的中断是通过虚拟中断控制器(VGIC)转发给物理中断控制器(PGIC)来实现的。当虚拟机需要处理中断时,VGIC会将中断发送给PGIC,PGIC会根据中断的优先级和路由信息将中断发送给相应的虚拟机。
系统调用
系统调用是指运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务。
系统提供用户程序与操作系统之间的接口
- 用户调用系统调用API,传递参数给应用程序。
- 应用程序进入内核空间,调用系统调用处理程序。
- 内核进行权限检查和参数验证,准备系统调用参数。
- 内核执行系统调用指令,将处理器从用户模式切换到内核模式,进入内核态。
- 处理器在内核模式下,保存现场,即保存用户态的寄存器状态等信息,以备将来返回用户空间时使用。
- 处理器执行系统调用处理程序,内核根据系统调用类型进行相应的处理,可能会访问设备或文件系统等。
- 处理完系统调用后,内核恢复现场,即恢复之前保存的寄存器状态等信息。
- 处理器退出内核模式,返回用户模式,即返回应用程序。
- 应用程序得到系统调用结果,进行后续处理。
例如我们运行库函数 getchar() ,会在库函数中找到其函数实体,并调用内核API接口使用汇编指令 SVC #0.
那么以下是从硬件视角进行分析的一张图。
以下是Linux系统常见的系统调用:
当发生系统调用时可以用ptrace()进行追踪系统调用的情况,strace追踪系统调用,ltrace追踪库函数的调用。
系统调用由于用户直接操作到了内核,所以其中的寄存器传参,以及具体的指针访问们必须经过严格的检验。但是如果进行完备的指针检测十分耗时,所以需要进行非全面检查。Linux非全面检查的房房是,初步检测用户指针是否属于对应进程 的用户内存区域最大可能边界,通过了初步检测,用户指针可能仍然非法。
对中断与异常的了解更深了一些
学习笔记贴,大佬们看看就好(狗头)
才知道不同处理器对异常和中断的处理也不同
很早很早以前,曾经花大力气学习dos系统的中断处理。没想到,今天还要花时间重新学习嵌入系统的中断处理,有趣儿,哈哈哈。