中断子系统

中断

Linux中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

中断子系统框架

image-20250901101222885

  • CPU

  • 中断控制器

  • 外设

  • 中断向量表

  • 中断号

  • Linux内核中断子系统

  • 中断编程接口(request_irq接口函数)

  • 具体的外设驱动

中断控制器

image-20250901102414888

GIC 是ARM 公司给Cortex-A/R 内核提供的一个中断控制器,类似Cortex-M 内核中的NVIC。GIC 接收众多的外部中断,然后对其进行处理,最终就只通过四个信号报给ARM 内核,这四个信号的含义如下:

  • VFIQ:虚拟快速FIQ。
  • VIRQ:虚拟外部IRQ。
  • FIQ:快速中断IRQ。
  • IRQ:外部中断IRQ。
image-20250901102917824
单核
image-20250901103251800 image-20250901103345111

上图中我们可以看到所有的中断都发送给了GIC控制器,它负责处理各种中断,判断优先级、屏蔽、使能等工作。在GIC控制器中有Distributor、Interface

**Distributor(分发器端):**块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个CPU Interface 上去.分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到CPU 接口端

  • 全局中断使能控制。

  • 控制每一个中断的使能或者关闭。

  • 设置每个中断的优先级。

  • 设置每个中断的目标处理器列表。

  • 设置每个外部中断的触发模式:电平触发或边沿触发。

  • 设置每个中断属于组0 还是组1。

**CPU Interface(CPU 接口端):**每个CPU Core 都可以在GIC 中找到一个与之对应的CPU Interface,CPU 接口端就是分发器和CPU Core 之间的桥梁

  • 使能或者关闭发送到CPU Core 的中断请求信号。

  • 应答中断。

  • 通知中断处理完成。

  • 设置优先级掩码,通过掩码来设置哪些中断不需要上报给CPU Core。

  • 定义抢占策略。

  • 当多个中断到来的时候,选择优先级最高的中断通知给CPU Core。

中断的分类

image-20250509154927955

GICV2

中断的种类可以分为:

  • SPI(Shared Peripheral Interrupt),共享外设中断,顾名思义,所有的核Core共享的中断,这个是最常见的,那些外部中断都属于SPI 中断(注意!不是SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的Core 都可以处理,不限定特定Core
  • PPI(Private Peripheral Interrupt)私有外设中断,我们说了GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
  • SGI(Software-generated Interrupt)软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用SGI 中断来完成多核之间的通信

ID (Hardware Interrupt ID) - 硬件中断号

中断源有很多,每一个CPU 最多支持1020 个中断ID,中断ID 号为ID0~ID1019。这1020 个ID 包含了PPISPISGI

  • ID0~ID15:这16 个ID 分配给SGI。

  • ID16~ID31:这16 个ID 分配给PPI。

  • ID32~ID1019:这988 个ID 分配给SPI,像GPIO 中断、串口中断等这些外部中断。I.MX6U 的总共使用了128 个中断ID,加上前面属于PPI 和SGI 的32 个ID,I.MX6U 的中断源共有128+32=160个。

《I.MX6ULL 参考手册》的3.2 小节可以查看I.MX6U 完整的中断源

image-20250509155510610

IRQ (Linux Virtual Interrupt Number) - Linux 虚拟中断号

Linux 内核软件视角下的逻辑编号。它是一个由 Linux 内核分配和管理的软件概念,是驱动开发者请求和使用的“虚拟中断号”。当驱动调用 request_irq(irq, handler, ...) 来注册一个中断处理函数时,它使用的就是这个 irq 号。

特性 IRQ (虚拟中断号) ID (硬件中断号)
视角 Linux 内核软件视角 中断控制器硬件视角
用途 驱动开发者使用 硬件工程师、底层配置使用
稳定性 相对稳定,但可能随配置变化 绝对固定,由芯片决定
关系 通过 irq_domain 映射到 ID 通过 irq_domain 被映射到 IRQ

中断控制器处理中断的流程

image-20250901105220714
多核GIC

GRC控制器结构:分为仲裁器CPU接口两部分

中断的状态可以分为Inactive、Pending、Active、Active and Pending四种状态:

状态 含义
Inactive 中断未发生,或已被处理完成。
Pending 中断已发生,但尚未被 CPU 处理。
Active CPU 正在处理该中断。
Active and Pending CPU 正在处理该中断,但同一中断源又发出了新的中断。
image-20250901105005072
  1. 中断发生(Inactive → Pending)

    • 设备发出中断信号。
    • 仲裁器将该中断状态设置为 Pending
  2. 仲裁器选择中断(Pending → 发送给 CPU Interface)

    • 仲裁器在所有 Pending 状态的中断中选择优先级最高的一个。
    • 将其发送给目标 CPU 的 CPU Interface。
  3. CPU Interface 判断是否转发给 CPU

    • CPU Interface 会检查:
      • 该中断的优先级是否高于当前 CPU 的屏蔽优先级(如优先级掩码寄存器)。
      • 或者是否是 CPU 正在处理的中断(用于支持中断嵌套或抢占)。
    • 如果条件满足,则向 CPU 发出中断信号(如 IRQ 或 FIQ)。
  4. CPU 响应中断(Pending → Active)

    • CPU 检测到中断信号后,跳转到中断处理程序。
    • CPU 通过读取中断应答寄存器(如 GICC_IAR)获取中断号。
    • 读取后,该中断状态由 Pending 变为 Active
  5. 中断处理中(Active)

    • CPU 执行该中断对应的处理函数(ISR, Interrupt Service Routine)。
  6. 中断完成(Active → Inactive)

    • CPU 处理完成后,写入中断结束寄存器(如 GICC_EOIR),通知控制器中断已完成。
    • 控制器将该中断状态改为 Inactive

Cortex-A

处理器运行模式

image-20250901214642570

除了User(USR)用户模式以外,其它8 种运行模式都是特权模式,这几个运行模式可以通过软件进行任意切换,也可以通过中断或者异常来进行切换。

寄存器组

ARM 架构提供了16 个32位的通用寄存器(R0R15)**供软件使用,前15 个(R0R14)可以用作通用的数据存储,R15 是**程序计数器PC,用来保存将要执行的指令

ARM 还提供了一个当前程序状态寄存器CPSR 和一个备份程序状态寄存器SPSRSPSR 寄存器就是CPSR 寄存器的备份。

image-20250901212343818

通用寄存器

R0~R15 就是通用寄存器,通用寄存器可以分为以下三类:

  • 未备份寄存器,即R0~R7。
  • 备份寄存器,即R8~R14。
  • 程序计数器PC,即R15。
  1. 未备份寄存器

    所有的处理器模式下这8 个寄存器都是同一个物理寄存器,在不同的模式下,这8 个寄存器中的数据就会被破坏。所以这8 个寄存器并没有被用作特殊用途。

  2. 备份寄存器

    • R8~R12 这5个寄存器有两种物理寄存器

      • 快速中断模式下(FIQ)它们对应着Rx_irq(x=812) 物理寄存器,因为FIQ 模式下的`R8R12`是独立的,因此中断处理程序可以不用执行保存和恢复中断现场的指令,从而加速中断的执行过程。
      • 其他模式下对应着Rx(8~12)物理寄存器。
    • 备份寄存器R13

      R13也叫做SP,用来做为栈指针。其中一个是用户模式(User)和系统模式(Sys)共用的,剩下的7 个分别对应7种不同的模式。基本上每种模式都有一个自己的R13物理寄存器,应用程序会初始化R13,使其指向该模式专用的栈地址,这就是常说的初始化SP指针。

    • 备份寄存器R14

      R14 也称为连接寄存器(LR),LR 寄存器在ARM 中主要用作如下两种用途:

      • 每种处理器模式使用R14(LR)来存放当前子程序的返回地址如果使用BL或者BLX
        来调用子函数的话,R14(LR)被设置成该子函数的返回地址,在子函数中,将R14(LR)中的值赋给R15(PC)即可完成子函数返回,比如在子程序中可以使用如下代码

        • MOV PC, LR @寄存器LR 中的值赋值给PC,实现跳转
          
          1
          2
          3
          4

          - ```
          PUSH {LR} @将LR 寄存器压栈----子函数的入口
          POP {PC} @将上面压栈的LR 寄存器数据出栈给PC 寄存器,严格意义上来讲应该是将@LR-4 赋给PC-----子函数的最后面
      • 异常模式对应的R14 寄存器被设置成该异常模式将要返回的地址

  3. 程序计数器R15

    程序计数器R15也叫做PC

    R15保存着当前执行的指令地址值加8个字节,原因是:ARM 处理器3 级流水线:取指->译码->执行,这三级流水线循环执行,比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在R15(PC)中。

    R15(PC)总是指向当前正在执行的指令地址再加上2条指令的地址。对于32 的ARM 处理器,每条指令是4个字节,所以:

    1
    R15 (PC)值 = 当前执行的程序位置 + 8 个字节。

状态寄存器

所有的处理器模式都共用一个CPSR 物理寄存器CPSR 是当前程序状态寄存器,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志等一些状态位以及一些控制位。

除了User和Sys这两个模式以外,其他7个模式每个都配备了一个专用的物理状态寄存器,叫做SPSR(备份程序状态寄存器)当特定的异常中断发生时,SPSR 寄存器用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后可以用SPSR中保存的值来恢复CPSR。

image-20250901214235189

CPSR 寄存器
1. 条件码标志位 (Condition Code Flags)

这些位由 ARM 指令(通常带有 S 后缀,如 ADDS)自动设置,用于决定条件指令(如 BGE, CMP)是否执行。

  • N (Negative / Less Than) [31]: 结果为负时置 1。
  • Z (Zero) [30]: 结果为零时置 1。
  • C (Carry / Borrow / Extend) [29]: 对于加法,无符号溢出时置 1;对于减法,无符号借位时置 0。
  • V (oVerflow) [28]: 有符号溢出时置 1。
2. 控制位 (Control Bits)

这些位用于控制 CPU 的核心行为。

  • I (IRQ disable) [7]: 置 1 表示禁用 IRQ 中断。这是中断处理中的关键位!

  • F (FIQ disable) [6]: 置 1 表示禁用 FIQ 中断

  • T (Thumb) [5]: 置 1 表示 CPU 处于 Thumb 状态,执行 Thumb 指令;置 0 表示处于 ARM 状态。

  • M[4:0] (Mode bits): 这 5 位决定了 CPU 当前处于哪种工作模式,这是理解特权级和中断的又一关键!

    • 模式 M[4:0] 用途 备注
      User 10000 用户模式,运行普通应用程序 非特权模式,资源受限
      FIQ 10001 快速中断模式,用于处理高速数据传输、DMA 等 特权模式
      IRQ 10010 普通中断模式,用于处理普通硬件中断 特权模式
      Supervisor 10011 监管模式,上电或软件中断 (swi) 指令进入,Linux 内核态 特权模式
      Abort 10111 中止模式,在内存访问失败时进入 特权模式
      Undef 11011 未定义模式,在遇到未知指令时进入 特权模式
      System 11111 系统模式,一种特权级的用户模式,用于运行特权操作系统任务 特权模式

CPSR 在中断处理流程中的关键作用

  1. 中断发生前 (CPU 在 User 模式)
    • CPSR.M[4:0] = 10000 (User 模式)
    • CPSR.I = 0 (IRQ 中断是使能的)
  2. 硬件自动响应中断
    • 当一个 IRQ 中断被 CPU Interface 发送给 CPU 核心后,CPU 硬件会自动执行以下操作:
      • 保存现场:将当前的 CPSR 保存到 SPSR_irq(IRQ 模式下的备份程序状态寄存器)中。
      • 模式切换:修改 CPSR.M[4:0] = 10010,让 CPU 立即切换到 IRQ 模式
      • 禁用中断自动置 CPSR.I = 1禁用新的 IRQ 中断,防止中断嵌套(除非编写代码手动开启)。
      • 切换执行流:将返回地址保存到 LR_irq,并跳转到中断异常向量表指定的 IRQ 处理函数。
  3. 中断处理中 (CPU 在 IRQ 模式)
    • 此时,CPU 运行在特权模式下,可以访问所有系统资源。
    • CPSR.I = 1,所以在此期间发生的普通 IRQ 中断会被屏蔽,但 FIQ 通常不会被屏蔽,以实现高速处理。
  4. 中断处理完成,恢复现场
    • 中断服务程序执行完毕后,需要返回到被中断的任务。
    • 执行一条特殊的返回指令(如 subs pc, lr, #4),这条指令会:
      • 将之前保存在 SPSR_irq 中的值自动写回 CPSR
      • 这条指令同时将 pc 指向被中断的指令地址,从而恢复执行。
    • 这一操作是原子性的,CPU 模式、中断屏蔽位(I/F)、条件标志位等所有状态都瞬间恢复到了中断前的样子。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// ===== 伪代码表示 ARM 中断处理流程 =====

// 1. 中断发生前 (CPU 在 User 模式)
CPSR = 0x00000010; // M[4:0] = 10000 (User 模式), I=0 (IRQ 使能)
PC = normal_execution; // 正常执行用户程序

// 2. 硬件自动响应中断 (当 IRQ 信号到达时,硬件自动执行以下操作)
// 注意:这些操作是原子性的,由硬件自动完成,不是软件指令
SPSR_irq = CPSR; // 保存当前 CPSR 到 SPSR_irq
CPSR.M = 0b10010; // 切换到 IRQ 模式 (M[4:0] = 10010)
CPSR.I = 1; // 禁用 IRQ 中断 (I=1)
LR_irq = PC + 4; // 保存返回地址 (由于流水线,实际是下一条指令地址)
PC = 0x00000018; // 跳转到 IRQ 异常向量地址

// 3. 软件异常向量处理 (在 0x00000018 处)
// 这是软件预先设置的处理程序
vector_irq:
// 矫正返回地址 (由于 ARM 流水线架构)
LR_irq = LR_irq - 4;

// 保存关键寄存器到 IRQ 栈
SP_irq = SP_irq - 8; // 调整栈指针
[SP_irq] = R0; // 保存 R0
[SP_irq+4] = LR_irq; // 保存返回地址

// 保存之前的 CPSR (来自 SPSR_irq)
temp = SPSR_irq;
[SP_irq+8] = temp;

// 准备切换到 SVC 模式
temp = CPSR;
temp.M = 0b10011; // SVC 模式
SPSR_svc = temp; // 设置 SPSR_svc 为 SVC 模式

// 根据中断前的模式分发处理
previous_mode = SPSR_irq.M; // 获取中断前的模式
if (previous_mode == 0b10000) // User 模式
PC = __irq_usr;
else if (previous_mode == 0b10011) // SVC 模式
PC = __irq_svc;
else
PC = __irq_invalid; // 其他模式不支持

// 4. 高级中断处理 (例如 __irq_usr)
__irq_usr:
// 保存所有用户寄存器到栈
push {r0-r12, lr}

// 调用通用中断处理程序
irq_number = get_irq_number(); // 从中断控制器获取中断号
handle_irq(irq_number); // 通用中断处理

// 恢复寄存器
pop {r0-r12, lr}

// 返回到用户模式
movs pc, lr; // 特殊返回指令,同时恢复 CPSR

// 5. 中断处理完成,恢复现场
// 当执行 movs pc, lr 或 subs pc, lr, #4 时:
// - 将 SPSR_irq 的值写回 CPSR (自动完成)
// - 将 LR_irq 的值赋给 PC
// 这一条指令原子性地完成了模式切换和程序流程恢复

// 最终状态:CPU 回到 User 模式,继续执行被中断的指令
CPSR = 0x00000010; // M[4:0] = 10000 (User 模式), I=0 (IRQ 使能)
PC = normal_execution; // 继续执行被中断的程序