Linux中断子系统1(基于Linux6.6)---综述
一、 Arm Linux 中断子系统
1.1. 中断硬件系统三部分
中断硬件系统通常由三个主要组成部分构成,这些组件共同工作,以确保中断的有效处理和管理。这三部分是:
1. 中断源(Interrupt Source)
中断源是产生中断信号的硬件设备或事件。中断源通常是外部设备或系统内部的硬件事件,如:
- 外部设备:例如键盘、鼠标、网络接口卡、定时器等硬件设备,它们会在特定事件发生时发出中断信号。
- 内部事件:例如处理器计时器溢出、硬件故障、系统错误等。
中断源负责生成中断请求信号(IRQ),并将其传送到中断控制器。
2. 中断控制器(Interrupt Controller)
中断控制器是处理器与中断源之间的桥梁。它的主要功能是管理多个中断源的请求、优先级和传递中断信号。中断控制器的主要任务包括:
- 接收中断请求(IRQ):当多个设备同时发出中断请求时,中断控制器接收这些请求。
- 优先级管理:不同的中断源可以设置不同的优先级,控制器根据优先级决定哪个中断先被处理。
- 中断屏蔽:允许或者禁止特定中断源的中断请求。当某些中断被禁止时,它们不会对处理器产生影响。
- 中断响应:控制器决定哪一个中断请求最先处理,并将中断信号传递给处理器。
常见的中断控制器包括:
- PIC(Programmable Interrupt Controller):可编程中断控制器,如 Intel 8259A。
- APIC(Advanced Programmable Interrupt Controller):更先进的中断控制器,支持更多的中断源和优先级管理,常见于现代多核处理器中。
3. 处理器(CPU)
处理器是中断系统的核心,负责处理中断信号并执行相应的中断处理程序。处理器在接收到中断控制器传递的中断信号后,按照以下步骤执行:
- 中断响应:处理器停止当前的执行,保存现场(如寄存器的状态),然后跳转到中断服务程序(Interrupt Service Routine, ISR)。
- 中断服务程序(ISR):中断服务程序是一段预定义的代码,它会处理特定的中断事件。例如,读取设备数据、清除中断标志、恢复系统状态等。
- 中断返回:在ISR处理完中断后,处理器恢复现场,继续执行中断发生前的任务。
4. 硬件中断系统的逻辑图
系统中有若干个CPU block用来接收中断事件并进行处理,若干个Interrupt controller形成树状的结构,汇集系统中所有外设的irq request line,并将中断事件分发给某一个CPU block进行处理。
从接口层面看,主要有两类接口,一是中断接口。二是控制接口。
+---------------------------------------------------+
| Interrupt Source |
| (External devices, Timer, IO, etc.) |
+---------------------------------------------------+
| ^
| Interrupt Request |
v |
+---------------------------------------------------+
| Interrupt Controller |
| (Manages priority, arbitration, IRQ signals) |
+---------------------------------------------------+
| ^
| IRQ Signal |
v |
+---------------------------------------------------+
| CPU |
| (Interrupt Acknowledge, Context Save, |
| Execute ISR, Restore Context) |
+---------------------------------------------------+
| ^
| Context Restore |
v |
+---------------------------------------------------+
| Interrupt Vector Table |
| (Contains ISR addresses for specific IRQs) |
+---------------------------------------------------+
1.2. Linux中断子系统四个部分
组成部分:普通外设驱动、Linux kernel通用中断处理模块(硬件无关代码)、CPU架构相关处理、中断控制器驱动代码。
1. 普通外设驱动(Device Driver)
- 作用:外设驱动是与具体硬件设备直接交互的代码,负责响应硬件中断并处理相应的硬件事件。
- 工作方式:当外设触发中断时,驱动程序的中断服务程序(ISR)会被调用,执行与该硬件相关的任务(例如读取输入数据、更新状态等)。外设驱动程序将处理与硬件相关的具体细节,并在中断服务程序(ISR)或软中断(Soft IRQ)中执行这些任务。
- 示例:网络设备驱动、磁盘控制器驱动、USB 驱动等。
2. Linux Kernel 通用中断处理模块(硬件无关代码)
- 作用:这一部分是 Linux 内核中与硬件无关的中断处理模块,主要负责中断请求的分发、调度以及基本的中断管理。
- 工作方式:
- 该模块管理中断的注册和注销,确保外设驱动可以在需要时正确地请求中断处理服务。
- 它负责为每个中断源分配一个中断号(IRQ),并将这些中断号与相应的中断服务程序(ISR)关联起来。
- 在处理多个中断时,内核会根据中断的优先级、处理需求等因素调度适当的中断服务程序。
- 示例:与中断相关的核心函数如
request_irq()
、free_irq()
,这些函数允许外设驱动请求和释放中断线。
3. CPU 架构相关处理(Architecture-specific code)
- 作用:每种 CPU 架构(如 x86、ARM、MIPS 等)都有其特定的中断机制和处理方式,这部分代码处理与特定架构相关的中断细节。
- 工作方式:不同的 CPU 架构可能有不同的中断控制方式、硬件特性、指令集等。因此,内核需要提供与具体架构相关的代码,来处理这些架构特定的中断机制。
- 中断向量表:不同架构的中断向量表可能有所不同。CPU 需要通过它来确定跳转到哪个中断服务程序。
- 中断启用和禁用:不同架构有不同的指令和机制用于启用或禁用中断。此部分代码会根据当前架构处理相应的中断控制操作。
- 中断上下文切换:不同的架构可能有不同的上下文切换机制,涉及保存和恢复寄存器状态等。
- 示例:
arch/x86/kernel/irq.c
或arch/arm/kernel/irq.c
中的代码,根据架构提供对中断的具体处理方法。
4. 中断控制器驱动代码(Interrupt Controller Driver)
- 作用:中断控制器是用来管理系统中所有硬件中断的设备,它负责分发和调度硬件中断到正确的处理程序。不同平台可能有不同类型的中断控制器。
- 工作方式:
- 中断控制器驱动程序的作用是初始化中断控制器,并在硬件产生中断时进行正确的分发。
- 它负责处理中断的屏蔽、优先级、触发方式等。根据硬件控制器的类型,可能会有不同的实现。
- 常见的中断控制器有 PIC(可编程中断控制器)、APIC(高级可编程中断控制器)、GIC(通用中断控制器) 等。
- 该驱动程序的工作还包括分配中断号、注册中断源、处理嵌套中断等任务。
- 示例:
- x86平台:
arch/x86/kernel/apic.c
负责管理本地 APIC。 - ARM平台:
drivers/irqchip/irq-gic.c
负责管理 GIC 中断控制器。
- x86平台:
1.3. 两种中断请求(ARM):
在 ARM 架构中,常见的两种中断请求是:
-
FIQ (Fast Interrupt Request):
FIQ 是一种快速中断请求,具有较高的优先级。它允许更快的响应时间,因此通常用于时间要求较高的任务,如实时处理。ARM 处理器在处理 FIQ 时,会保存的寄存器更少,使得它能更快地进入中断服务程序。 -
IRQ (Interrupt Request):
IRQ 是常规的中断请求,优先级较低。它用于处理一般的中断,如外设输入、定时器中断等。ARM 处理器在处理中断时,会保存更多的寄存器,以保证能在中断处理后恢复到正确的执行状态。
这两种中断的主要区别在于优先级和响应时间,FIQ 的优先级高于 IRQ。
1.4. 几个重要数据结构
-
irq_desc
irq_desc
是用来描述一个中断的核心数据结构。每个中断源都有一个irq_desc
,它包含了与中断相关的所有信息,如中断处理程序、状态信息等。
-
irq_data
irq_data
存储了与特定中断源相关的硬件信息。它通常包含了设备的中断号、标志位以及其他硬件层次的信息。
-
irq_chip
irq_chip
是表示中断控制器的驱动程序结构。它封装了中断控制器的操作,比如使能/禁用中断、设置触发类型、查询中断状态等。irq_chip
是操作硬件中断控制器的接口。
-
irqaction
irqaction
结构表示一个中断源的具体处理动作。它包含了注册中断时需要的信息,比如中断处理程序(ISR)、触发条件、参数等。
数据结构关系图
+-----------------------------------------------------+
| irq_desc |
| 描述一个中断源,包含了中断的所有管理信息 |
| |
| +------------------+ +--------------------+ |
| | irq_chip | | irqaction | |
| | 中断控制器的操作 | | 中断源的处理动作 | |
| +------------------+ +--------------------+ |
| |
| +------------------+ |
| | irq_data | <- 硬件相关信息,指向中断源 |
| | 中断数据 | |
| +------------------+ |
+-----------------------------------------------------+
1.5. 数据结构的管理辅助函数
1. generic_handle_irq()
- 作用:这是一个用来处理中断的通用函数。它会根据中断号(IRQ)在内核中找到对应的中断描述符,并调用与该中断相关的处理程序。
- 功能:当硬件中断发生时,内核会触发一个中断处理流程。
generic_handle_irq()
负责执行这个过程,查找并调用注册的中断处理函数。 - 使用场景:这个函数通常在中断处理上下文中调用,由中断服务例程 (ISR) 使用。
generic_handle_irq(irq);
其中 irq
是中断号,表示你要处理的具体中断。
2. irq_to_desc()
- 作用:将中断号(IRQ)转换为对应的中断描述符(
irq_desc
)。 - 功能:每个中断号都与一个
irq_desc
结构关联,irq_to_desc()
函数会根据给定的中断号返回对应的中断描述符。irq_desc
结构包含了与该中断相关的各种信息,比如中断的类型、触发方式、控制器信息等。 - 使用场景:通常用于内核中断子系统,在处理中断时需要获取中断的详细信息。
struct irq_desc *desc = irq_to_desc(irq);
其中 irq
是中断号。
3. irq_set_chip()
- 作用:设置一个中断控制器的操作集 (chip operations)。
- 功能:每个中断控制器都需要一个对应的中断芯片 (
irq_chip
),它定义了如何处理中断的各种操作,例如使能、禁用中断、确认中断等。irq_set_chip()
函数用于将一个中断控制器的操作集与某个中断描述符 (irq_desc
) 绑定。 - 使用场景:这个函数通常在初始化时调用,用于设置中断的处理方式,确保与硬件中断控制器正确对接。
irq_set_chip(irq, &my_irq_chip);
其中 irq
是中断号,my_irq_chip
是包含中断控制器操作的 irq_chip
结构。
4. irq_set_chained_handler()
- 作用:设置中断链式处理程序。
- 功能:某些硬件设备的中断需要一个链式中断处理机制,也就是说,当一个中断触发时,多个处理程序可能需要依次执行。
irq_set_chained_handler()
允许你为一个中断号设置链式处理程序,并且指定一个 "父" 处理程序,这样中断处理会按照一定的顺序进行。 - 使用场景:用于处理多级中断或链式中断的情形,例如一个中断触发后,首先由一个主控制器处理,然后传递给另一个设备的中断处理。
irq_set_chained_handler(irq, my_chained_handler);
其中 irq
是中断号,my_chained_handler
是你自定义的链式处理程序。
1.6. 中断处理标准函数
1. handle_simple_irq()
- 作用:用于处理简单的中断。这些中断通常不涉及复杂的流控机制。
- 功能:这是一个最简单的中断流控处理程序,适用于那些在触发时不会进行复杂状态切换的中断类型。它将中断的处理程序直接传递给内核的中断调度器,并执行相应的任务。
- 使用场景:适用于不需要特别复杂的处理中断,如不需要电平或边沿检测的普通中断。
void handle_simple_irq(struct irq_desc *desc)
{
// 执行简单的中断处理
}
2. handle_level_irq()
- 作用:用于处理电平触发的中断。
- 功能:电平触发中断在中断信号持续为高电平时持续触发中断,因此需要特别的流控机制来确保不会反复触发相同的中断。
handle_level_irq()
会在中断处理时考虑电平状态,防止中断被重复响应,直到电平信号变为低电平。 - 使用场景:用于那些电平触发的中断,例如当设备在持续运行时不会自动关闭中断信号,直到处理完成后才会清除中断。
void handle_level_irq(struct irq_desc *desc)
{
// 处理电平触发的中断
// 检查电平状态并确保不会重复触发
}
3. handle_edge_irq()
- 作用:用于处理边沿触发的中断。
- 功能:边沿触发中断只在信号从低电平跳变到高电平(上升沿)或从高电平跳变到低电平(下降沿)时触发一次。因此,
handle_edge_irq()
会检测中断的边沿事件,并在检测到有效的边沿时才会处理该中断。 - 使用场景:适用于那些基于边沿触发的硬件中断,如 GPIO 引脚的上升沿或下降沿触发。
void handle_edge_irq(struct irq_desc *desc)
{
// 处理边沿触发的中断
// 只在边沿(上升沿或下降沿)发生时处理
}
4. handle_fasteoi_irq()
- 作用:用于处理需要显式
EOI (End of Interrupt)
的中断。 - 功能:某些硬件中断控制器(如 8259 PIC 或 APIC)需要显式地发送一个
EOI
信号来表示中断已处理完毕。handle_fasteoi_irq()
会在处理完中断后,立即向中断控制器发送EOI
信号,表示中断处理完成,允许下一次中断触发。 - 使用场景:适用于那些需要明确结束中断的硬件控制器,如一些外部中断控制器。
void handle_fasteoi_irq(struct irq_desc *desc)
{
// 处理需要EOI的中断
// 在处理完中断后,向控制器发送EOI信号
}
5. handle_percpu_irq()
- 作用:处理单个 CPU 上响应的中断。
- 功能:
handle_percpu_irq()
主要是用于处理中断时的流控。具体来说,当某个中断触发时,只有特定的一个 CPU 需要去响应和处理这个中断,这就避免了多 CPU 上的重复响应和处理。这个处理过程包括获取中断描述符并执行相应的中断处理程序。 -
使用场景
- 单核 CPU 中断:一些硬件设备的中断可能只需要在某个特定的 CPU 上处理。例如,如果某个外设直接与某个 CPU 绑定,那么只有该 CPU 才需要处理该外设的中断。
- 中断流控:为了避免多核系统中多个 CPU 同时处理中断带来的资源争用,
handle_percpu_irq()
确保每个中断只会在指定的 CPU 上进行处理。
void handle_percpu_irq(struct irq_desc *desc, unsigned int cpu);
1.7. 提供给驱动的API
1. enable_irq()
void enable_irq(unsigned int irq);
功能:该函数用于启用一个特定的中断。中断一旦被启用,硬件设备发出的中断信号将会被内核处理。通常,硬件设备的中断被禁用后,必须通过 enable_irq()
手动启用,以便使该设备可以再次触发中断。
使用场景:在某些情况下,中断可能会被暂时禁用(例如为了防止中断处理程序在某些操作过程中被频繁触发)。此时可以调用 enable_irq()
来恢复中断的处理。
2. disable_irq()
void disable_irq(unsigned int irq);
功能:该函数用于禁用指定的中断。禁用后,硬件设备发出的中断信号将不会被内核处理。通常在处理中断时,可能需要禁止某个中断(例如,防止中断处理程序在某些条件下重复触发)。
使用场景:如果你正在处理某个中断并希望在处理过程中避免该中断再次触发(例如防止中断的递归或重复触发),可以使用 disable_irq()
来暂时禁用该中断。
3. disable_irq_nosync()
void disable_irq_nosync(unsigned int irq);
功能:与 disable_irq()
相似,但是 disable_irq_nosync()
不会等待当前正在处理中断的完成。这个函数只会禁用中断,而不会同步中断处理的状态。
使用场景:当你需要迅速禁用中断,而不关心当前处理中断的情况时,可以使用 disable_irq_nosync()
。该函数不会等待正在处理中断的完成,因此适用于某些特殊的性能优化场景。
4. request_threaded_irq()
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long flags,
const char *devname, void *dev_id);
功能:这个函数用于请求一个中断并为该中断指定一个线程化的处理程序。与传统的中断处理程序不同,request_threaded_irq()
允许你将中断处理分为两个阶段:
handler
:首先会调用这个函数进行快速的中断处理。thread_fn
:然后调用这个线程化的函数进行更长时间的处理或后续的工作。
使用场景:当中断处理工作比较繁重,可能会阻塞或延迟响应时,可以使用线程化中断处理。通过 request_threaded_irq()
,内核可以在一个独立的线程中执行较为复杂的处理,从而避免阻塞中断上下文。
5. irq_set_affinity()
int irq_set_affinity(unsigned int irq, const cpumask_t *cpumask);
功能:该函数用于设置中断的 CPU 亲和性。CPU 亲和性意味着你可以指定哪些 CPU 核心可以处理某个特定的中断。通过设置亲和性,可以将中断绑定到一个或多个特定的 CPU 核心,以提高缓存一致性、减少上下文切换开销、平衡负载等。
使用场景:在多核系统中,合理设置中断的 CPU 亲和性可以显著提高系统性能。例如,可以将某些 I/O 密集型设备的中断绑定到特定的 CPU 上,从而优化性能并避免其他 CPU 的负载过重。
1.8. 中断子系统初始化:
1.中断向量表拷贝
start_kernel()--->setup_arch()
start_kernel()--->trap_init()
2.irq_desc结构体分配资源、填充默认值
start_kernel()--->early_irq_init()<初始化irq_desc结构体desc = alloc_desc(i, node, 0, NULL, NULL);irq_insert_desc(i, desc);>---->arch_early_irq_init()
3.初始化irq_desc、注册中断控制器驱动
start_kernel()--->init_IRQ()--->machine_desc()--->init_irq()--->irqchip_init()--->
of_irq_init(__irqchip_of_table);
__irqchip_of_table
通常是一个数组或者结构体数组,每个元素描述一个中断控制器的相关信息。例如,它可能包含中断控制器的类型、地址、以及初始化该控制器的必要函数。
在 ARM 系统中,这个表格可能会包含类似于 ARM 通用中断控制器(GIC)等中断控制器的配置信息。每个中断控制器条目可能会包含以下字段:
chip
: 指向描述中断控制器的结构体指针,例如gic_chip
。flags
: 一些标志位,用于控制中断控制器的行为(如是否跳过某些设置)。
举个例子:
static const struct of_irq_chip __irqchip_of_table[] = {
{
.chip = &gic_chip,
.flags = IRQCHIP_SKIP_SET_WAKE,
},
// 其他中断控制器可以在这里添加
};
在上面的示例中,__irqchip_of_table
是一个数组,包含了一个或多个中断控制器的描述符。在此例中,它描述了 ARM 通用中断控制器(GIC)的配置。
1.9. 中断发生时,CPU硬件动作
+----------------------------+
| 中断触发 |
+----------------------------+
|
v
+----------------------------+
| 保存上下文 (R0~R15, R14) |
+----------------------------+
|
v
+----------------------------+
| 保存CPSR到SPSR |
+----------------------------+
|
v
+----------------------------+
| 修改CPSR为中断模式,屏蔽中断 |
+----------------------------+
|
v
+----------------------------+
| PC跳转到异常向量表中的地址 |
+----------------------------+
1.10. 中断处理流程
+----------------------------+
| 外部中断产生 |
+----------------------------+
|
v
+----------------------------+
| CPU保存上下文(寄存器、CPSR)|
+----------------------------+
|
v
+----------------------------+
| 调用asm_do_IRQ() |
+----------------------------+
|
v
+----------------------------+
| 调用handle_IRQ() |
+----------------------------+
|
v
+----------------------------+
| 调用generic_handle_irq() |
+----------------------------+
|
v
+----------------------------+
| 调用generic_handle_irq_desc() |
+----------------------------+
|
v
+----------------------------+
| 调用desc->handle_irq() |
+----------------------------+
|
v
+----------------------------+
| 判断IRQ类型 |
+----------------------------+
|
+----+----+
| |
v v
+----------------------------+ +----------------------------+
| handle_level_irq() | | handle_edge_irq() |
+----------------------------+ +----------------------------+
| |
v v
+----------------------------+ +----------------------------+
| 调用handle_irq_event() | | 调用ack_irq() |
+----------------------------+ +----------------------------+
| |
v v
+----------------------------+ +----------------------------+
| 执行action->handler | | 调用handle_irq_event() |
+----------------------------+ +----------------------------+
| |
v v
中断处理完成 中断处理完成
二、 GIC_V2V3
2.1. ARM中断控制器 GIC(V2):
GIC400原理框图
输入: SGI(Software-generated Interrupts),软件中断(ID0~ID15)
PPI(Private Peripheral Interrupt),CPU私有中断(ID16~ID31)
SPI(Shared Peripheral Interrupt),CPU间共有中断(ID32~ID1019)
注: GIC400(arm_v7)只支持480个SPI,GIC600(arm_v8)支持960个SPI
2.2. GIC-V3支持ARMv8架构
输入:
LPI(Locality-specific Peripheral Interrupts ),基于消息的中断
SGI(Software-generated Interrupts),软件中断(ID0~ID15)
PPI(Private Peripheral Interrupt),CPU私有中断(ID16~ID31)
SPI(Shared Peripheral Interrupt),CPU间共有中断(ID32~ID1019)
GIC 逻辑分区与 ITS(Interrupt Translation Service) 的结构
上图简化:
+------------------+ +----------------------------+
| GIC Distributor| <-------- | ITS (Interrupt Translation |
| | | Service) |
+------------------+ +----------------------------+
| |
v v
+---------------------+ +-----------------------------+
| CPU Interfaces | | Interrupt Sources (Devices) |
| (CPU0, CPU1, ...) | +-----------------------------+
+---------------------+ |
| v
v +---------------------------+
+--------------------------+ | Interrupt Request |
| Interrupt Targets | | Distribution |
| (CPU0, CPU1, ...) | +---------------------------+
+--------------------------+
三、 ARM_V8(arm64)中断机制
3.1. Exception level
Arm_v7a~arm_v8,中断机制引入一个新概念“Exception level”,用于整合之前架构中的processor mode和privilege level相关的功能,同时提供了完整的“security model”和“virtualization框架”的支持。
具有更高的控制权和访问能力。EL0 至 EL3 分别代表了不同的权限等级。
Exception Level (EL) 简介
ARMv8 的异常级别如下:
-
EL0 (Exception Level 0):
- 权限:非特权(Non-privileged)。
- 主要用于执行用户级应用程序,操作系统及硬件的直接访问受到限制。普通应用程序通常运行在此级别。
-
EL1 (Exception Level 1):
- 权限:特权(Privileged)。
- 用于运行操作系统内核(如 Linux、Android 等)。EL1 允许访问更多的系统资源和硬件控制,但仍不具有最高的系统权限。
-
EL2 (Exception Level 2):
- 权限:特权(Privileged)。
- 主要用于虚拟化支持。EL2 是运行虚拟化管理程序(Hypervisor)时使用的级别,它允许虚拟化系统(如 KVM)进行底层硬件的管理,同时提供给虚拟机一个受控的执行环境。
-
EL3 (Exception Level 3):
- 权限:特权(Privileged)。
- EL3 主要用于处理系统的安全管理,尤其是用于 安全监控模式(Secure Monitor Mode)。它通常用于启动过程中的安全引导、硬件级安全和虚拟化的安全处理,处理与硬件安全相关的操作,如 TrustZone。
权限级别与控制
随着 n 的增大,权限也随之增加。具体来说,ELn 的权限级别如下:
- EL0:用户模式,不允许访问系统资源。
- EL1:内核模式,允许操作系统控制大部分硬件资源。
- EL2:虚拟化模式,用于管理虚拟机和虚拟化环境。
- EL3:安全模式,控制系统的硬件安全性和加密,通常用于硬件的安全管理。
ARMv8 Exception Level 权限图
可以将这些不同的 Exception Level 对应的权限用一个图示进行表示,如下所示:
+---------------------+----------------------+
| EL3 | Privileged Level |
| (Secure Monitor) | Highest privilege |
+---------------------+----------------------+
| EL2 | Privileged Level |
| (Hypervisor Mode) | Virtualization |
+---------------------+----------------------+
| EL1 | Privileged Level |
| (Kernel Mode) | OS level |
+---------------------+----------------------+
| EL0 | Non-Privileged |
| (User Mode) | User Application |
+---------------------+----------------------+
各个 EL 之间的转换
ARMv8 允许在不同的 Exception Level 之间进行转换。转换通常发生在特定的异常或中断发生时。不同 EL 之间的转换控制包括:
- EL0 → EL1:用户程序发起系统调用时,会从 EL0 切换到 EL1。
- EL1 → EL2:操作系统内核或虚拟化管理程序可以在需要时切换到 EL2 以进行虚拟化管理。
- EL2 → EL3:在某些安全操作或系统启动时,系统会从 EL2 切换到 EL3 以执行安全相关任务。
3.2. 异步异常和同步异常
1. 异步异常:
CPU CORE外部产生,IRQ、FIQ、ERROR。
2.同步异常:
CPU内部产生如:未定义的指令、data abort、prefetch instruction abort、SP未对齐异常、debug exception及SVC/HVC/SMC指令。
3.3. 涉及的寄存器
3.3.1.通用寄存器:
x0~x30共计31个;
3.3.2.SP寄存器:
每个exception level都有自己的stack pointer register,名字是SP_ELx,当然,前提是处理器支持该EL。对于EL0,只能使用SP_EL0,EL1~EL3,除了可以使用自己的SP_ELx,还可以选择使用SP_EL0;
3.3.3.SPSR寄存器:
当发生异常的时候,PE总是把当前cpu的状态保存在SPSR寄存器中,该寄存器有三个,分别是SPSR_EL1,SPSR_EL2,SPSR_EL3,异常迁移到哪一个exception level就使用哪一个SPSR。在返回异常现场的时候,可以使用SPSR_ELx来恢复PE的状态;
3.3.4.PC寄存器:
当发生异常的时候,PE把一个适当的返回地址保存在ELR(Exception Link Register)寄存器中,该寄存器有三个,分别是ELR_EL1,ELR_EL2,ELR_EL3,异常迁移到哪一个exception level就使用哪一个ELR。在返回异常现场的时候,可以使用ELR_ELx来恢复PC值;
3.3.5.VBAR寄存器:
各个exception level的Vector Base Address Register (VBAR)寄存器,该寄存器保存了各个exception level的异常向量表的基地址。该寄存器有三个,分别是VBAR_EL1,VBAR_EL2,VBAR_EL3。
具体的exception handler是通过vector base address + offset得到,offset的定义如下表所示:
Exception offset 对照表:
exception level迁移情况 |
Synchronous exception的offset值 |
IRQ和vIRQ exception的offset值 |
FIQ和vFIQ exception的offset值 |
SError和vSError exception的offset值 |
同级exception level迁移,使用SP_EL0。例如EL1迁移到EL1 |
0x000 |
0x080 |
0x100 |
0x180 |
同级exception level迁移,使用SP_ELx。例如EL1迁移到EL1 |
0x200 |
0x280 |
0x300 |
0x380 |
ELx迁移到ELy,其中y>x并且ELx处于AArch64状态 |
0x400 |
0x480 |
0x500 |
0x580 |
ELx迁移到ELy,其中y>x并且ELx处于AArch32状态 |
0x600 |
0x680 |
0x700 |
0x780 |
3.4. 中断向量表
ARM64 中的中断向量表
ARM64 中的异常向量表结构通常是这样的:
- 向量表的入口点按照异常类型进行划分,每个异常类型对应一个固定的入口位置。
- 每个组包含 4 个条目,4 个条目分别对应不同类型的异常。
各个异常类型
这 16 个条目对应 4 类异常,每类异常包含 4 个不同的入口,分别是:
1. Synchronous Aborts (同步中止)
同步中止(Synchronous Aborts)是指由于程序在执行过程中遇到错误或访问非法内存地址等问题时发生的异常。通常这种异常包括:
- 数据访问错误(Data Abort)
- 指令访问错误(Instruction Abort)
这些异常通常是同步发生的,它们与当前执行的指令密切相关,通常是由于访问无效的内存、权限错误或其他类似原因导致的。
- 对应条目位置:该异常对应向量表中的前 4 个条目之一,具体是由异常发生的类型(如访问权限错误、地址不对齐等)决定的。
2. IRQ (普通中断)
IRQ(Interrupt Request)是普通的硬件中断。当外部设备(如定时器、外部硬件、I/O 操作等)需要引起处理器的注意时,通常会触发 IRQ。IRQ 中断是异步的,意味着它与当前执行的指令无关。
- 对应条目位置:通常与中断控制器(如 GIC)的配置有关,这些中断是由外部设备或硬件发起的。
3. FIQ (快速中断)
FIQ(Fast Interrupt Request)是高优先级的中断。它通常用于响应一些高优先级的硬件事件,比如高速度的I/O设备,它的响应时间比普通 IRQ 更短,因此它在 ARM 处理器中通常会有比普通 IRQ 更高的优先级。
- 对应条目位置:FIQ 和 IRQ 一样是硬件中断,但它具有更高的优先级。处理器会优先响应 FIQ,而忽略低优先级的 IRQ。
4. SError (系统错误)
SError(System Error)是 ARM 架构中的一个特定中断类型,用来处理硬件错误或系统级的严重问题。SError 中断通常用于处理如硬件故障、内存错误、总线错误等问题,这些异常可能是由硬件引起的,因此其优先级通常非常高。
- 对应条目位置:SError 异常通常对应向量表中的特定条目,发生时会中断当前执行并跳转到该异常的处理入口。
向量表的结构
根据 ARM64 架构中的规范,中断向量表包含 16 个条目,这些条目按以下方式组织,每组有 4 个条目(每组对应一种异常类型):
+---------------------------------------+
| Exception Vector Table |
|---------------------------------------|
| Entry 0 - Synchronous Aborts |
| Entry 1 - Synchronous Aborts |
| Entry 2 - Synchronous Aborts |
| Entry 3 - Synchronous Aborts |
|---------------------------------------|
| Entry 4 - IRQ |
| Entry 5 - IRQ |
| Entry 6 - IRQ |
| Entry 7 - IRQ |
|---------------------------------------|
| Entry 8 - FIQ |
| Entry 9 - FIQ |
| Entry 10 - FIQ |
| Entry 11 - FIQ |
|---------------------------------------|
| Entry 12 - SError |
| Entry 13 - SError |
| Entry 14 - SError |
| Entry 15 - SError |
+---------------------------------------+
每个组都包含对应的中断或异常处理入口,当 ARM 处理器发生某种类型的异常时,它会跳转到该组中的相应入口点进行处理。
在 Linux 中,EL1 异常向量表由内核在启动时初始化,并根据不同的异常类型来跳转到特定的处理程序。
异常向量表的布局
通常,EL1 异常向量表的布局如下所示:
+---------------------------------------------------------------+
| Entry 0 - Reset (重置) |
| Entry 1 - Undefined Instruction (未定义指令异常) |
| Entry 2 - Supervisor Call (SVC) (超级调用异常) |
| Entry 3 - Prefetch Abort (预取中止异常) |
| Entry 4 - Data Abort (数据中止异常) |
| Entry 5 - Reserved (保留) |
| Entry 6 - IRQ (普通中断请求) |
| Entry 7 - FIQ (快速中断请求) |
| Entry 8 - Error (系统错误) |
| Entry 9 - Reserved (保留) |
| Entry 10 - Reserved (保留) |
| Entry 11 - Reserved (保留) |
| Entry 12 - Reserved (保留) |
| Entry 13 - Reserved (保留) |
| Entry 14 - Reserved (保留) |
| Entry 15 - Reserved (保留) |
+---------------------------------------------------------------+
具体来说,各个异常的含义如下:
1. Entry 0: Reset (重置)
- 该条目是当处理器复位时执行的代码,系统从此处开始执行,通常是从
start_kernel()
等函数初始化开始。
2. Entry 1: Undefined Instruction (未定义指令)
- 该异常发生在 CPU 执行了一个无法识别的指令时,通常是由于指令编码错误、程序计数器被修改或者指令集不兼容所导致。该异常由处理器硬件自动触发,通常由内核的未定义指令处理程序捕获。
3. Entry 2: Supervisor Call (SVC) (超级调用)
- SVC 异常是由软件触发的系统调用,通常通过
svc
指令触发。操作系统通过此异常来进行系统调用处理,它是用户态进程与内核态之间的接口。
4. Entry 3: Prefetch Abort (预取中止)
- 该异常发生在 CPU 执行预取指令时,无法从内存中加载指令的情况下。通常是因为访问了非法地址或内存保护错误。
5. Entry 4: Data Abort (数据中止)
- 该异常发生在 CPU 访问数据时遇到问题,通常是访问了非法的内存地址、权限错误、地址不对齐等情况。数据中止可以通过内核中的异常处理程序来捕获并进行适当的错误处理。
6. Entry 6: IRQ (普通中断请求)
- 该条目用于处理来自硬件设备的普通中断。IRQ 是由硬件触发的请求,通常用于处理外部设备(如定时器、I/O 等)的事件。
7. Entry 7: FIQ (快速中断请求)
- FIQ 是一种高优先级的中断,请求处理时,优先级高于普通 IRQ。FIQ 用于处理高优先级、时间敏感的任务,像是实时系统中对硬件事件的快速响应。
8. Entry 8: Error (系统错误)
- 系统错误处理程序捕获硬件或软件中的严重错误,例如总线错误、硬件故障等。这些错误通常是致命的,且需要在内核级别进行相应的错误恢复或报告。
9. Entry 9 - Entry 15: Reserved (保留)
- 这些条目是保留的,用于未来的扩展或特定的硬件配置。通常情况下,系统不会在这些条目上跳转,除非硬件或操作系统发生了扩展或特定配置。
3.5. 中断控制器驱动注册:
start_kernel()--->init_IRQ()--->irqchip_init()--->of_irq_init(__irqchip_of_table)
__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的驱动代码初始化函数和设备树compatible string的对应关系。
drivers/irqchip/irq-gic-v3.c
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);将gic_v3的驱动绑定为gic_of_init。
3.6. IRQ中断处理流程:
el0_irq/el1_irq--->irq_handler--->handle_arch_irq(handle_arch_irq为全局回调函数)
在gic_of_init--->gic_init_bases--->set_handle_irq(gic_handle_irq)中将gic_handle_irq传给了handle_arch_irq;
gic_handle_irq--->handle_domain_irq--->__handle_domain_irq--->generic_handle_irq--->
generic_handle_irq_desc--->desc->handle_irq(desc)<根据具体irq_desc注册的回调函数处理>