首先借助STM32我们需要了解中断系统是如何构成的
会有一个中断源,也就是能够向CPU发出中断请求的设备或事件。中断源不分硬件和软件,也就是产生中断信号,就会执行中断服务函数
但是CPU是如何知道中断源产生后就找到对应的中断服务函数呢,这个时候就要引入中断向量表,它的主要功能是描述中断对应的中断服务函数,每个中断源都有一个唯一的中断号(也称向量号),CPU在接收到中断信号后,会读取中断号,并将其作为索引到中断向量表中查找对应的中断服务函数地址,找到地址后,CPU会跳转到该地址执行中断服务函数其实中断向量表类似于一张表格,表格是按照内存地址由低到高,里面的每一个地址都存放一个函数
一般来说,中断向量表都是链接到代码的最前面,对于ARM处理器都是从0X00000000开始运行的。但是如下图博主截下来的图片可以看出在STM32中,设置的链接首地址被设置在0X8000000,所以中断向量表就无法正确的与中断服务函数对应
针对于代码一定要从非0X000000的地方开始运行,那么就需要引入中断向量偏移这个概念
通俗来讲就是将中断向量表移动到地址开始的地方,通过改变中断向量表在内存中的位置来避免冲突和限制,确保CPU能够正确地响应中断并执行相应的中断服务程序。
STM32的中断向量偏移就是在这里设置的, 设置SCB的VTOR寄存器为新的中断向量表起始地址即可
NVIC(内嵌向量中断控制器),简单来说就是中断的管理机构,这对于STM32这类 Cortex-M 内核的芯片,中断管理系统叫NVIC,但是在IMX6U中使用的是Cortex-A7内核的中断管理系统为GIC(general interrupt controller),待会儿会详细介绍
中断服务函数就是产生中断后,会执行的额外操作
那么对于STM32的中断有了大概的回顾过后,现在就要开始引入IMX6U也就是Cortex-A的内核下的中断系统,首先还是从中断向量表开始
可以看到此内核有八个中断异常,但是其实严格来算只有7个。按照博主参考的教程上面说的是一款芯片有什么中断都是可以从中断向量表中看出来的,它和STM32有很大的区别在于,我们平时在看的时候,可以看到STM32有很多外设的中断,例如I2C,或者定时器,但是这里都没有,并不是我们的Cortex-A内核芯片只有这么几个中断
主要是Cortex-A和Cortex-M内核芯片之间有很大的区别,对于STM32的内核来说,中断向量表中列举了一款芯片所有的中断向量,包括芯片外设的所有中断。对于Cortex-A内核CPU的所有外部中断都属于上面的IRQ中断,也就是外部中断模式,当任意一个外部中断产生的时候会触发IRQ中断。在IRQ中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,更具具体的中断做出对应的处理,上面这段话博主也是从教程当中找到,并且理解到的,其中还附上了一张图,也能够较为直观的说明它们的关系
在左侧列举出的所有中断,都是包含在IRQ中断中的IMX6U中断,不管左边图片中哪个中断被触发都会直接触发IRQ中断,所以需要在IRQ中断服务函数中判断究竟是左侧的哪个中断发生了,才能做出具体的处理
在这里再依次介绍一下上面七种中断对应的意思:
1. 复位中断(Rest)CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面 做一些初始化工作,比如初始化 SP 指针、DDR 等等
2. 未定义指令中断(Undefined Instruction) 如果指令不能识别的话就会产生此中断。
3. 软中断(Software Interrupt,SWI) 由 SWI 指令引起的中断,Linux 的系统调用会用 SWI 指令来引起软中断,通过软中断来陷入到内核空间。
4. 指令预取中止中断(Prefetch Abort) 预取指令的出错的时候会产生此中断。
5. 数据访问中止中断(Data Abort) 访问数据出错的时候会产生此中断。
6. IRQ 中断(IRQ Interrupt) 外部中断,前面已经说了,芯片内部的外设中断都会引起此 中断的发生。
7. FIQ 中断(FIQ Interrupt) 快速中断,如果需要快速处理中断的话就可以使用此中断。
在上面的七个中断种,我们常用的也就是复位中断和IRQ中断,所以我们需要编写的就是这两个中断的中断服务函数
在这里讲解完中断向量表后,在我们编写中断向量表后,按照前面的讲解,自然也是需要设置中断向量地址偏移的,毕竟我们代码链接的地址是0X87000000,所以需要偏移到这个地址的最上层
讲解完中断向量表和地址偏移后,接下来就是中断控制器了,在STM32中叫做NVIC,但是在Cortex-A的内核中的中断控制器叫做GIC(Generic Interrupt Controller),按照博主参考的教程,GIC一共有四个版本,分别为V1、V2、V3、V4,但是由于V1非常古老,所以就废弃了,对于V3和V4来说是供64位芯片的使用,所以对于AEM架构来说合适的基本只有V2,所以主要讲解的也是GIC V2
GIC V2 最多支持 8 个核。ARM 会根据 GIC 版本的不同研发 出不同的 IP 核,那些半导体厂商直接购买对应的 IP 核即可,比如 ARM 针对 GIC V2 就开发出 了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是 ARM 内核只提供了四个信号给 GIC 来汇报中断情况:VFIQ、VIRQ、FIQ 和 IRQ,关系图如下
GIC 接收众多的外部中断,然后对其进行处理,最终就只通过四个信号 报给 ARM 内核,这四个信号的含义如下,VFIQ:虚拟快速 FIQ。 VIRQ:虚拟外部 IRQ。 FIQ:快速中断 IRQ。 IRQ:外部中断 IRQ。
VFIQ 和 VIRQ 是针对虚拟化的,我们这里不讲解虚拟化,剩下的就是 FIQ 和 IRQ 了。我们只使用 IRQ,所以相当于 GIC 最终向 ARM 内核就上报一个 IRQ 信号。我们来看一下GIC的工作原理,也就是上报原理是什么
ARM的cpu,特别是cortex-A系列的CPU,目前都是多core的cpu,因此对于多core的cpu的中断管理,就不能像单core那样简单去管理,由此arm定义了GICv2架构,来支持多核cpu的中断管理。,首先我们先看GIC控制器这个地方,首先GIC将众多的中断源分为三类
1. SPI(Shared Peripheral Interrupt) 名为共享中断,所有的核心共享的中断,这个是最常见的,对于那些外部中断都属于SPI中断,例如三件中断,串口中断等,这些中断所有的核心都可以处理,不指定核心
2. PPI((Private Peripheral Interrupt) 私有中断,在之前提到过,GIC是支持多核的,每个核都会有自己独有的中断。这些都有的中断肯定是需要指定对应的核心来处理,所以这些中断也叫做私有中断
3. SGI(Software-generated Interrupt) 软件中断,由软件触发引起的中断,通过向寄存器 GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
在使用GIC时,中断ID(interruput ID)是用来表示特定中断源的编号。每个中断源都有唯一的中断ID,用于区分不同的中断类型和源。GIC通过中断ID来管理路由中断
通俗来讲也就是为了区分不同的中断,引入了中断号。中断源有很多,为了区分这些不同的中断源肯定要给它们分配一个唯一的ID,这些ID就是中断ID,每一个CPU最多支持1020个中断ID,在1020个中断ID中,包含了PPI、SPI、SGI,我们需要了解这三类中断源是如何分配这些ID的
ID0-ID15是给SGI的,ID16-ID31是给PPI,剩余的ID32-ID1019给SPI,也就是按键中断等外部中断。这个ID有什么用呢?
当时在前面也描述过,对于所有产生的外部中断触发都只有IRQ这个中断模式,而IRQ只有一个中断服务函数,所以需要在IRQ的中断服务函数中获取到触发当前中断的中断号,再次通过对应的中断号找到对应的中断处理函数
在我们移植的NXP官方SDK库中的文件MCIMX6Y2C.h,在文件中定义了一个枚举类型IRQN_Type,此枚举类型就枚举出了IMX6U的所有中断,篇幅有限,就不全部截取了
从上图以及下图,我么可以看到GIC架构分为了两个逻辑块:Distributor 和 CPU Interface,它们分别叫做分发器断和CPU接口端
分发器(Distributor),此模块负责将中断进行优先级划分(prioritization)并且转发到指定的CPU interface中,与Distributor相关的寄存器均以GICD_作为前缀。Distributor在多处理器系统中的关键作用:它集中管理所有中断源,决定它们的优先级,并将优先级最高的中断有效地分发给系统中的各个CPU或核心
其实简单来说,系统中所有的中断源都连接在分发器上,可以通过其中的仲裁单元控制中断源的属性,例如优先级,状态等,分发器把中断输出到CPU interface,后者决定将哪个中断转发给CPU核心
分发器需要完成的主要工作包括 全局中断使能控制、控制每一个中断的使能或关闭、设置每个中断的优先级、设置每个中断的目标处理器列表、设置每个外部中断的触发模式:电平触发或边沿触发、设置每个中断组(例如Group0或者1),转发SGI中断到一个或者多个目标处理器等
CPU接口单元 (CPU Interface),CPU核心通过控制器的CPU接口单元接收中断。CPU接口单元寄存器用于屏蔽,识别和控制和转发到CPU核心的中断的状态。系统中的每个CPU核心都有一个单独的CPU接口
CPU核心都可以在GIC中找到一个对应的CPU Interface。它就是分发器和CPU核心之间的桥梁
它的主要作用就是 使能或者关闭发送到CPU核心的中断请求信号、确认一个中断(也就是完成中断的应答),通知中断处理完成,设置优先级掩码,通过掩码来设置哪些中断不需要上报给CPU核心等
整个中断的处理流程,首先是中断收集与缓存,当系统某个外设或设备产生中断信号的时候,GIC首先先收集这些中断请求。GIC内部的中断分分发器负责检测所有源的中断信号,并根据优先级等信息等中断进行管理,收集到的中断请求会被暂时缓存在分发器中,这些中断处于pending(等待中)的状态
然后再进行中断优先级判断与选择,GIC的分发器会根据各个中断的优先级来决定哪些中断需要优先处理。分发器会选择优先级高低顺序,依次发送到处理器
被选择的中断信号,GIC将中断请求通过CPU接口(GIC CPU Interface)发送到处理器,一般CPU接口寄存器保存了一个中断号,这个中断号对应的是当前需要处理的中断来源设备或外设,
此时,GIC将该中断的状态pending(等待中)变为active(处理中),意味着该中断正在进行处理
GIC在后续将中断号发送到CPU接口,处理器通过读取AIR寄存器来获取中断号。每个产生中断的信号都会得到一个中断号,也就是他们的标识符,用于确定哪个外设或设备产生了中断
读取完中断号后,每个中断号通常会在中断向量表中有一个对应的入口(其实一般就是地址),上面也说过了,中断向量表是处理器在处理中断时用来寻找对应,中断服务程序(ISR)的关键部分。包含了每个中断号对应的ISR的地址,处理器通过中断号查找中断向量表中对应的入口,找到中断服务程序的地址,一旦确定了ISR的入口地址,处理器通过跳转到该地址来执行相应的中断服务程序
在完成跳转以后,开始执行中断服务程序(ISR),ISR会根据中断号的类型执行一系列处理步骤,例如读取外设状态、进行数据传输、清楚外设的中断标志等。ISR在执行期间,中断状态保持为active,表示当前中断正在处理,但是这里做一点提示,ISR通常应该尽量短小,以便快速完成,避免其他中断被长时间延迟处理。复杂的处理可以推迟到非中断的时候进行
当CPU完成中断服务程序的处理后,它需要通知GIC该中断已经处理完毕。这是通过写入结束中断寄存器(EOI,End of Interrupt Register)来完成的。在完成中断处理后,CPU将该中断号写入GIC的EOI
寄存器,通知GIC该中断已经完成,可以清除该中断状态。
GIC收到处理器的EOI信号后,GIC会将该中断的状态从active状态清除,并将其重置为**idle(空闲)**状态,表示该中断处理已完成,系统可以再次接收该中断的请求。如果还有其他等待处理的中断,GIC分发器会继续根据优先级选择下一个中断请求,并重复上述过程。
在最后,如果在处理中断A时,又有更高优先级的中断B产生,GIC会中断当前的中断服务程序,处理优先级更高的中断。这称为中断嵌套。处理器会保存当前中断的状态,转而处理新的高优先级中断。完成后,再恢复并继续处理原始的中断。
但是,在我们多核处理器(MPU)中,多数多是使用的GIC中断控制器,一般来说是不需要频繁的使用中断嵌套的,多加个的一个显著优势就是可以通过并行处理,也就是多个核心同时份工作,来分担工作负载
因为多核架构的并行性,允许多个CPU核心并行处理不同的任务或中断。当有多个中断请求时,GIC可以将不同的中断分配给不同的核心处理器,这样每个核心可以独立处理其分配的中断,而不会出现中断嵌套的情况。例如,GIC可以将优先级较高的中断分配给一个核心,而将低优先级的中断分配给另一个核心。这样即便是多个高优先级中断发生,也可以同时在不同的核心上得到处理,而无需打断正在处理的其他中断。
中断的分发机制,在多核系统中,GIC通过其分发器可以将中断请求发送给多个CPU接口。每个CPU接口负责一个核心,这样就可以在不同的核心上并行处理中断,而不是依赖单个核心去处理所有中断。GIC Distributor能够对中断进行智能分发。它根据中断的类型、优先级以及核心的负载情况,将中断发送到合适的核心处理器。这减少了单核系统中频繁嵌套中断的需求。
以及考虑到中断嵌套的成本来说,中断嵌套会增加上下文切换的开销。在单核处理器中,嵌套中断会导致处理器频繁保存和恢复中断处理上下文,这不仅消耗时间,还会增加栈的使用量,影响系统性能。
所以对于多核系统来说,中断分发中断分发的机制主要有以下几种方式,进一步减少了对中断嵌套的需求,某些中断可以被配置为始终发送到特定的核心处理器进行处理。这样,系统可以将常见的中断集中到少数核心上处理,而其他核心专注于执行应用程序或其他任务。
GIC可以采用轮询机制,将中断轮流分配到各个核心处理器。这样即便多个中断同时发生,也可以平衡地分配给各核心进行处理。
系统可以静态也可以动态,对于静态来说系统,可以配置某些中断源始终由某个特定的核心处理器处理,这样可以减少动态分配时带来的负载不平衡和开销。静态分配的中断一般不会产生嵌套的需求,因为核心只负责处理它指定的中断。
动态的情况,系统可以根据核心的负载情况动态分配中断。比如,当某个核心负载较低时,GIC可以将更多的中断发送到该核心进行处理,而不是让所有中断集中在一个核心上,从而避免过多的中断嵌套。
虽然多核系统中频繁的中断嵌套并不常见,但在某些情况下,嵌套中断仍然是有用的,单个核心上处理优先级极高的中断:如果一个核心在处理较低优先级的中断时,发生了极高优先级的中断请求,系统可能仍然需要抢占低优先级中断来处理更重要的任务。这通常是非常重要的实时系统设计中的需求。在多核处理器中的某些实时任务场景,某些高优先级的中断不能延迟处理,此时可能会允许嵌套高优先级的中断。
总之在多核处理器系统中,使用GIC可以通过并行处理中断,显著减少对中断嵌套的需求。GIC通过中断分发器可以将不同的中断分配给多个核心,充分利用多核架构的并行性,避免频繁的上下文切换和中断嵌套带来的性能损耗。不过,特定情况下(如实时性要求很高的场景),嵌套中断仍然可能被使用,但总体来说,多核系统能够有效地降低对复杂中断嵌套的依赖。
下一讲我们就开始具体实验的讲解了,第一步肯定就是编写中断向量表了,基本原理就讲到这里了