inux中断-硬中断
0、前言
中断子系统有一次大改造,先mark一下
https://lore.kernel.org/all/20200521200513.656533920@linutronix.de/
arm64参考:Linux 中断管理机制
1、概述
广义上的中断主要分为同步中断和异步中断两种
- 同步(synchronous)中断:当指令执行时由CPU控制单元产生的。
- 异步(asynchronous)中断:由其他硬件设备依照CPU时钟信号随机产生的。
在x86和arm中,其实同步中断就是异常,而异步中断则是我们常说的中断(interrupt)。而我们本文中主要讨论的就是后者,异步中断。
也可以理解为异步中断是由中断控制器处理的,arm上是GIC(Generic Interrupt Controller),而x86上叫做APIC(Advanced Programmable Interrupt Controllers),而异步中断则是在CPU内完成的
2、硬件中断
个人理解认为,异步中断就是我们常提到的硬中断,由外设产生的中断信号,经由中断控制器交由对应的CPU进行处理。
而在linux中,我们又可以将硬件中断(异步中断)分为如下如下两类:
- 可屏蔽中断:可以通过设置eflags寄存器的IF标志来屏蔽对应CPU的中断,
sti
开启中断,cli
屏蔽中断。- 普通硬件中断:例如I/O设备完成数据传输、定时器到期等。
- 核间中断:用于在多核处理器系统中实现核间通信。它们通常用于处理进程调度、进程迁移等任务。
- 不可屏蔽中断:NMI通常用于处理关键错误和紧急任务,如硬件故障、系统崩溃等,需要立即得到处理,印象中很多BMC发过来的中断都是不可屏蔽中断。
3-中断控制器
3.1-APIC(x86)
可以参考:高级可编程中断控制器(APIC)
现代SMP架构的处理器的APIC由两部分组成,一个是local APIC,一个是I/O APIC。其中local-APIC是存在于CPU内的,外部的中断设备通常是通过I/O APIC设备,转发给对应的local APIC。
通常来说LAPIC 主要处理以下中断:
- APIC Timer 产生的中断(APIC timer generated interrupts)
- Performance Monitoring Counter 在 overflow 时产生的中断(Performance monitoring counter interrupts)
- 温度传感器产生的中断(Thermal Sensor interrupts)
- LAPIC 内部错误时产生的中断(APIC internal error interrupts)
- 本地直连 IO 设备 (Locally connected I/O devices) 通过 LINT0 和 LINT1 引脚发来的中断
- 其他 CPU (甚至是自己,称为 self-interrupt)发来的 IPI(Inter-processor interrupts)
- IOAPIC 发来的中断
而IOAPIC (I/O Advanced Programmable Interrupt Controller) 属于Intel芯片组的一部分。他负责连接外部的I\O设备,负责接收其发来的外部中断,通常IOAPIC 有 24 个 input 管脚(INTIN0~INTIN23),没有优先级之分。
3.1.1-APIC驱动
x86平台的APIC驱动主要放在arch/x86/kernel/apic/
中
3.2-GIC(ARM64)
对于ARM64来说,中断控制器被叫做GIC(Generic Interrupt Controller)
。
GIC的整体功能与APIC类似,但是对于GIC来说,是不存在IDT
这个概念的,ARM64主要通过异常向量表
来实现中断处理函数的处理。在GICv3(还没见过使用GICv4的soc)中中断主要分为如下几种
-
SGI (Software Generated Interrupt):
软件生成的中断,用于处理器内核之间的通信。SGI是由软件显式触发的,通常用于实现进程调度、负载均衡等功能。一个处理器可以向其他处理器或自己发送SGI。在x86中被定义为IPI。
-
SPI (Shared Peripheral Interrupt):
共享外设中断,用于处理与多个处理器核心共享的外设事件。SPI是系统范围内的中断,可以被路由到任何处理器。这些中断通常用于处理通用硬件事件,例如USB、网络、磁盘等。
-
PPI (Private Peripheral Interrupt):
私有外设中断,用于处理与特定处理器核心关联的外设事件。PPI是针对单个处理器的,每个处理器都有自己的一组PPI。这些中断通常用于处理与特定处理器相关的硬件事件,例如定时器、错误检测等。
-
LPI (Locality-specific Peripheral Interrupt):
局部特定外设中断,是GICv3引入的一种新中断类型。LPI用于处理与特定处理器组(例如NUMA节点)关联的外设事件。LPI相对于SPI具有更高的可扩展性,可以支持大量的中断源。这些中断通常用于处理大型多处理器系统中的硬件事件。
3.2.1-GIC驱动
GICv3的驱动主要在drivers/irqchip/irq-gic-v3.c
中,GIC设备的初始化有两种方式,一种是通过dtsi设备树,一种是通过ACPI从BIOS里直接初始化,具体的调用如下
- dtsi
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
设备在初始化的过程中,或尝试解析对应的设备树文件,对GIC进行初始化
在设备树中会如下定义一个GIC,以rk3399具体说明(scripts/dtc/include-prefixes/arm64/rockchip/rk3399.dtsi
)
gic: interrupt-controller@fee00000 { # 定义一个名为gic的节点,表示一个位于0xfee00000地址的中断控制器。
compatible = "arm,gic-v3";
#interrupt-cells = <4>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller; # 表示这个节点代表一个中断控制器。
# 定义了GIC中的各个寄存器地址和大小。
reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
<0x0 0xfef00000 0 0xc0000>, /* GICR */
<0x0 0xfff00000 0 0x10000>, /* GICC */
<0x0 0xfff10000 0 0x10000>, /* GICH */
<0x0 0xfff20000 0 0x10000>; /* GICV */
# 定义了GIC的中断信息,这里指定了一个PPI类型的中断,中断号为9,触发类型为高电平触发。
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>;
# 定义了一个名为its的节点,表示一个位于0xfee20000地址的MSI控制器。
its: msi-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
ppi-partitions { # 描述PPI中断的分区信息。定义了两个PPI分区:ppi_cluster0和ppi_cluster1。
ppi_cluster0: interrupt-partition-0 { # 绑定在小核0-3上
affinity = <&cpu_l0 &cpu_l1 &cpu_l2 &cpu_l3>;
};
ppi_cluster1: interrupt-partition-1 { # 绑定在大核0-1上
affinity = <&cpu_b0 &cpu_b1>;
};
};
};
这其中GIC的寄存器分别代表如下内容
- GICD(GIC Distributor):GIC分发器,负责管理和分发中断。GICD维护了一个中断优先级表,可以将中断分发给特定的CPU或一组CPU。
- GICR(GIC Redistributor):GIC再分发器,用于支持多个处理器。每个处理器都有一个关联的GICR,用于处理该处理器的SGI(Software Generated Interrupt)和PPI(Private Peripheral Interrupt)。
- GICC(GIC CPU Interface):GIC CPU接口,提供了处理器和GIC之间的接口。处理器通过GICC接收中断,发送EOI(End of Interrupt)信号,以及获取当前正在处理的中断等。
- GICH(GIC Virtual Interface):GIC虚拟接口,用于支持虚拟化。GICH提供了一个虚拟列表,用于存储来自虚拟机的中断。
- GICV(GIC Virtual CPU Interface):GIC虚拟CPU接口,提供了虚拟机和GIC之间的接口。虚拟机通过GICV接收中断,发送EOI信号,以及获取当前正在处理的中断等。
- acpi
Linux内核可以通过ACPI来获取硬件信息和配置。Linux内核包含了一个ACPI解释器,可以解析和执行ACPI表中的AML代码。
IRQCHIP_ACPI_DECLARE(gic_v3, ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR,
acpi_validate_gic_table, ACPI_MADT_GIC_VERSION_V3,
gic_acpi_init);
4、中断映射
4.1-IDT x86
我们通过上述的期间可以实现中断硬件上的捕捉、分发,但是在linux中通常在接受到中断后还会继续执行软件操作,而内核则是通过中断描述符表(Interrupt Descriptor Table)
来将不同的中断指向特定的程序入口,而中断处理的程序则被称为门(gates)
。因此初始化IDT是内核启动阶段重要的工作。
IDT被CPU加载在idtr
寄存器中,用以发生中断时分配中断的处理函数,通过idtr
中的idt
基地址+中断号偏移,从而获取中断处理函数的入口地址。
在x86架构的Linux内核中,IDT主要存储了中断向量与其对应的底层中断处理程序(也称为中断处理程序的入口点)之间的映射关系。当发生中断时,处理器会根据IDT找到对应的底层中断处理程序,并跳转到这个程序的入口点开始执行。然后,底层中断处理程序会调用内核的中断处理子系统,最后由内核的中断处理子系统调用相应的处理函数(例如my_irq_handler)来处理中断。
内核通过idt_setup_apic_and_irq_gates
函数初始化IDT
。
void __init idt_setup_apic_and_irq_gates(void)
{
int i = FIRST_EXTERNAL_VECTOR;
void *entry;
idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true); // 设置APIC and SMP idt,通常是错误中断、IPI
for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) { // 将外部IRQ中断处理程序的入口设置到IDT中
entry = irq_entries_start + IDT_ALIGN * (i - FIRST_EXTERNAL_VECTOR); // 中断处理函数入口
set_intr_gate(i, entry);// 设置中断门
}
#ifdef CONFIG_X86_LOCAL_APIC
for_each_clear_bit_from(i, system_vectors, NR_VECTORS) { // 将本地APIC IRQ中断处理程序的入口设置到IDT中
/*
* Don't set the non assigned system vectors in the
* system_vectors bitmap. Otherwise they show up in
* /proc/interrupts.
*/
entry = spurious_entries_start + IDT_ALIGN * (i - FIRST_SYSTEM_VECTOR); // 中断处理函数入口
set_intr_gate(i, entry); // 设置中断门
}
#endif
......
idt_setup_done = true; // 完成IDT的初始化设置
}
其中对应的底层处理函数入口具体被定义在了如下代码中
SYM_CODE_START(irq_entries_start)
vector=FIRST_EXTERNAL_VECTOR
.rept NR_EXTERNAL_VECTORS
UNWIND_HINT_IRET_REGS
0 :
ENDBR
.byte 0x6a, vector
jmp asm_common_interrupt // 跳转到asm_common_interrupt标签对应的位置,这个位置定义了一段通用的IRQ处理程序
/* Ensure that the above is IDT_ALIGN bytes max */
.fill 0b + IDT_ALIGN - ., 1, 0xcc
vector = vector+1
.endr
SYM_CODE_END(irq_entries_start)
SYM_CODE_START(spurious_entries_start)
vector=FIRST_SYSTEM_VECTOR
.rept NR_SYSTEM_VECTORS
UNWIND_HINT_IRET_REGS
0 :
ENDBR
.byte 0x6a, vector
jmp asm_spurious_interrupt
/* Ensure that the above is IDT_ALIGN bytes max */
.fill 0b + IDT_ALIGN - ., 1, 0xcc
vector = vector+1
.endr
SYM_CODE_END(spurious_entries_start)
其中asm_common_interrupt
与asm_spurious_interrupt
被定义在了如下代码中
/* Device interrupts common/spurious */
DECLARE_IDTENTRY_IRQ(X86_TRAP_OTHER, common_interrupt);
#ifdef CONFIG_X86_LOCAL_APIC
DECLARE_IDTENTRY_IRQ(X86_TRAP_OTHER, spurious_interrupt);
#endif
继续通过宏展开将二者关联起来
#define DECLARE_IDTENTRY_IRQ(vector, func) \
DECLARE_IDTENTRY_ERRORCODE(vector, func)
#define DECLARE_IDTENTRY_ERRORCODE(vector, func) \
asmlinkage void asm_##func(void); \
asmlinkage void xen_asm_##func(void); \
__visible void func(struct pt_regs *regs, unsigned long error_code)
最后声明了一个名为common_interrupt
和spurious_interrupt
的函数,而这两个函数实际的定义则是
DEFINE_IDTENTRY_IRQ(common_interrupt)
{
struct pt_regs *old_regs = set_irq_regs(regs); // 保存寄存器状态
......
desc = __this_cpu_read(vector_irq[vector]); // 获取vector中断号对应的中断描述符
if (likely(!IS_ERR_OR_NULL(desc))) { // 如果中断描述符有效
handle_irq(desc, regs); // 调用handle_irq处理函数,这里进一步的就会调用到注册过的irq回调函数
} else {
apic_eoi();
if (desc == VECTOR_UNUSED) {
pr_emerg_ratelimited("%s: %d.%u No irq handler for vector\n",
__func__, smp_processor_id(),
vector);
} else {
__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);// 当前CPU上的无效的中断描述符设置为VECTOR_UNUSED。
}
}
set_irq_regs(old_regs); // 恢复寄存器状态
}
外部中断会先跳到common_interrupt
的入口函数中,并且通过查找对应的vector_irq
来获取中断描述符,再进一步通过注册中断的时候绑定的desc->handler
来执行对应的中断处理函数。
以及
DEFINE_IDTENTRY_IRQ(spurious_interrupt)
{
handle_spurious_interrupt(vector);
}
4.2-中断向量表arm64
在arm64中,多了一个irq_domian
的概念,他是irq_desc->irq_data
中的一个成员。
struct irq_data {
u32 mask;
unsigned int irq;
unsigned long hwirq;
struct irq_common_data *common;
struct irq_chip *chip;
struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
这里可以看到,irq
就是中断在linux的中断虚拟中断号
,而hwirq
则是对应中断的实际中断号,irq_domain作为一个映射层,将硬件中断号映射到内核中的虚拟中断号。而硬件中断号hwirq
则是定义在产生中断的设备所对应的设备树文件或者bios文件中的。
而中断映射的过程则是通过发生的硬件中断hwirq,找到内核对应的virq,并运行对应的中断处理函数。
而hwirq与virq的则是设备的驱动在初始化的时候可以调用 irq_of_parse_and_map 这个接口函数进行该 device node 中和中断相关的内容的解析,并建立映射关系。
ARM64是通过异常向量表来实现中断的处理和映射的,其存在放VBAR_EL1
和VBAR_EL2
之中。
而发生硬件中断后,则会根据中断向量表进行跳转,其被定义在了arch/arm64/kernel/entry.S
SYM_CODE_START(vectors)
kernel_ventry 1, t, 64, sync // Synchronous EL1t
kernel_ventry 1, t, 64, irq // IRQ EL1t
kernel_ventry 1, t, 64, fiq // FIQ EL1t
kernel_ventry 1, t, 64, error // Error EL1t
kernel_ventry 1, h, 64, sync // Synchronous EL1h
kernel_ventry 1, h, 64, irq // IRQ EL1h
kernel_ventry 1, h, 64, fiq // FIQ EL1h
kernel_ventry 1, h, 64, error // Error EL1h
kernel_ventry 0, t, 64, sync // Synchronous 64-bit EL0
kernel_ventry 0, t, 64, irq // IRQ 64-bit EL0
kernel_ventry 0, t, 64, fiq // FIQ 64-bit EL0
kernel_ventry 0, t, 64, error // Error 64-bit EL0
kernel_ventry 0, t, 32, sync // Synchronous 32-bit EL0
kernel_ventry 0, t, 32, irq // IRQ 32-bit EL0
kernel_ventry 0, t, 32, fiq // FIQ 32-bit EL0
kernel_ventry 0, t, 32, error // Error 32-bit EL0
SYM_CODE_END(vectors)
根据发生中断的级别不同,有不同的跳转逻辑,EL0下的IRQ会跳转到
asmlinkage void noinstr el0t_64_irq_handler(struct pt_regs *regs)
{
__el0_irq_handler_common(regs);
}
EL1下的IRQ会跳转到
asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
{
el1_interrupt(regs, handle_arch_irq);
}
5、中断注册
5.1-注册中断号
由于从硬件触发的中断对应着具体的中断号,这样才能在内核中通过注册对应中断号的软件处理函数,所以在连接好中断的硬件电路后,我需要为这个这个硬件中断分配一个中断号。
- x86
对于x86来说,这个中断号是定义在BIOS里的,内核通过ACPI来获取对应设备的终端号。
- ARM64
对于ARM64来说,通过设备树初始化的平台,可以通过定义在dtsi中的文件来解析具体的中断号
/* 从设备树节点中获取中断信息 */
int irq = irq_of_parse_and_map(dev_node, 0); // 第二个参数0表示获取第一个中断信息
对于使用能了ACPI的ARM设备,也会同x86一样,通过ACPI接口直接从BIOS中获取对应设备的中断号。
以UFS设备的中断注册为例
int platform_get_irq_optional(struct platform_device *dev, unsigned int num)
{
int ret;
#ifdef CONFIG_SPARC
/* sparc does not have irqs represented as IORESOURCE_IRQ resources */
if (!dev || num >= dev->archdata.num_irqs)
goto out_not_found;
ret = dev->archdata.irqs[num];
goto out;
#else
struct fwnode_handle *fwnode = dev_fwnode(&dev->dev);
struct resource *r;
if (is_of_node(fwnode)) { // 如果是设备树节点
ret = of_irq_get(to_of_node(fwnode), num); // 通过设备获取中断号
if (ret > 0 || ret == -EPROBE_DEFER)
goto out;
}
r = platform_get_resource(dev, IORESOURCE_IRQ, num);
if (is_acpi_device_node(fwnode)) { // 如果是acpi节点
if (r && r->flags & IORESOURCE_DISABLED) {
ret = acpi_irq_get(ACPI_HANDLE_FWNODE(fwnode), num, r); // 通过ACPU获取中断号
if (ret)
goto out;
}
}
......
#endif
out_not_found:
ret = -ENXIO;
out:
if (WARN(!ret, "0 is an invalid IRQ number\n"))
return -EINVAL;
return ret;
}
EXPORT_SYMBOL_GPL(platform_get_irq_optional);
5.2-注册对应中断处理函数
在我们获取到了具体设备中断所对应的中断号后,就可以利用这个中断号对中断处理函数进行注册。
#include <linux/interrupt.h>
#include <linux/kernel.h>
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
printk(KERN_INFO "Interrupt handled: irq %d\n", irq);
return IRQ_HANDLED;
}
#include <linux/interrupt.h>
#include <linux/module.h>
static int my_irq = 123; // Replace with the actual IRQ number
static int __init my_module_init(void)
{
int ret;
ret = request_irq(my_irq, my_interrupt_handler, IRQF_SHARED, "my_interrupt", NULL);
if (ret) {
printk(KERN_ERR "Failed to register interrupt handler: %d\n", ret);
return ret;
}
printk(KERN_INFO "Registered interrupt handler for IRQ %d\n", my_irq);
return 0;
}
static void __exit my_module_exit(void)
{
free_irq(my_irq, NULL);
printk(KERN_INFO "Unregistered interrupt handler for IRQ %d\n", my_irq);
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
6、中断处理流程
6.1-x86
6.2-arm64
发生一个 EL0的irq时,根据中断向量表
kernel_ventry 0, t, 64, irq // IRQ 64-bit EL0
可以看到会跳转到制定的汇编代码,并传入特定的参数
.macro kernel_ventry, el:req, ht:req, regsize:req, label:req
.align 7
.Lventry_start\@:
.if \el == 0
/*
* This must be the first instruction of the EL0 vector entries. It is
* skipped by the trampoline vectors, to trigger the cleanup.
*/
b .Lskip_tramp_vectors_cleanup\@
.if \regsize == 64
mrs x30, tpidrro_el0
msr tpidrro_el0, xzr
.else
mov x30, xzr
.endif
.Lskip_tramp_vectors_cleanup\@:
.endif
sub sp, sp, #PT_REGS_SIZE
#ifdef CONFIG_VMAP_STACK
/*
* Test whether the SP has overflowed, without corrupting a GPR.
* Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
* should always be zero.
*/
add sp, sp, x0 // sp' = sp + x0
sub x0, sp, x0 // x0' = sp' - x0 = (sp + x0) - x0 = sp
tbnz x0, #THREAD_SHIFT, 0f
sub x0, sp, x0 // x0'' = sp' - x0' = (sp + x0) - sp = x0
sub sp, sp, x0 // sp'' = sp' - x0 = (sp + x0) - x0 = sp
b el\el\ht\()_\regsize\()_\label
保存完寄存器状态后,则会跳转到el0t_64_irq_handler
->__el0_irq_handler_common
->el0_interrupt
static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
{
el0_interrupt(regs, handle_arch_irq);
}
这里可以看到有绑定了一个中断的回调函数,这里的回调是对应的gic的处理函数,在gic初始化的过程中绑定
static int __init gic_init_bases(phys_addr_t dist_phys_base,
void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
......
set_handle_irq(gic_handle_irq);
......
}
通过gic_handle_irq
->__gic_handle_irq_from_irqson
进一步调用
static void __gic_handle_irq_from_irqson(struct pt_regs *regs)
{
bool is_nmi;
u32 irqnr;
irqnr = gic_read_iar(); // 获取当前中断的硬件中断号
is_nmi = gic_rpr_is_nmi_prio();
if (is_nmi) {
nmi_enter();
__gic_handle_nmi(irqnr, regs); // 如果是nmi中断执行特定函数
nmi_exit();
}
if (gic_prio_masking_enabled()) {
gic_pmr_mask_irqs();
gic_arch_enable_irqs();
}
if (!is_nmi)
__gic_handle_irq(irqnr, regs); // 进一步执行中断调用
}
而__gic_handle_irq
中则利用irqnr
进一步获取中断的号
static void __gic_handle_irq(u32 irqnr, struct pt_regs *regs)
{
if (gic_irqnr_is_special(irqnr))
return;
gic_complete_ack(irqnr);
if (generic_handle_domain_irq(gic_data.domain, irqnr)) {
WARN_ONCE(true, "Unexpected interrupt (irqnr %u)\n", irqnr);
gic_deactivate_unhandled(irqnr);
}
}
int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq)
{
return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
}
EXPORT_SYMBOL_GPL(generic_handle_domain_irq);
而在irq_resolve_mapping(domain, hwirq)
则是通过domain,来查找hwirq对应的irq_desc,最终再通过最初绑定的handler来调用真正的中断处理函数。
8、irq优先级
对于linux kernel而言,本身是不存在中断优先级区分的,这部分工作则由中断控制器来实现,通过编程设置不同中断的优先级,来实现接收到的外部中断按照优先级发送给CPU进一步响应。
在arm64中,gicv3是在如下位置进行的中断优先级设定
gic_dist_init
->gic_dist_config
void gic_dist_config(void __iomem *base, int gic_irqs,
void (*sync_access)(void))
{
unsigned int i;
/*
* Set all global interrupts to be level triggered, active low.
*/
for (i = 32; i < gic_irqs; i += 16)
writel_relaxed(GICD_INT_ACTLOW_LVLTRIG,
base + GIC_DIST_CONFIG + i / 4);
/*
* Set priority on all global interrupts.
*/
for (i = 32; i < gic_irqs; i += 4)
writel_relaxed(GICD_INT_DEF_PRI_X4, base + GIC_DIST_PRI + i); // 从32号中断开始,将所有的ppi外部中断设定为一样的优先级
......
}
需要补充说明的是,这里处理的其实都是irq优先级,这与中断的优先级
是包含关系的,在arm64中:
中断和异常有一个固定的优先级顺序,这个优先级顺序是由ARM的硬件设计决定的,不同的中断和异常类型有不同的优先级。当多个中断或异常同时发生时,硬件会按照这个优先级顺序来决定首先处理哪一个。
以下是ARM架构中的中断和异常的优先级顺序:
- 复位(Reset):这是最高优先级的异常,当系统复位时会触发这个异常。
- 数据中止(Data Abort):这个异常是由于内存访问错误(如非法访问、访问未对齐等)引起的。
- FIQ(Fast Interrupt):这是一种高优先级的中断,通常用于需要快速响应的硬件事件。
- IRQ(Interrupt Request):这是一种普通优先级的中断,用于处理常规的硬件事件。
- 预取中止(Prefetch Abort):这个异常是由于指令预取错误(如访问非法地址等)引起的。
- 未定义指令(Undefined Instruction)和软件中断(Software Interrupt):这两种异常的优先级是最低的,当执行一条未定义的指令或者软件触发一个中断时,会产生这两种异常。