一、中断控制器是什么?

大家好,我是你们的嵌入式系统讲师。今天我们来聊聊中断控制器,这个在嵌入式开发中绕不开的话题。

中断控制器,说白了就是CPU的"秘书"。CPU平时忙着跑程序,突然有外设说"我有急事要处理",这时候中断控制器就登场了。它负责接收各个外设的中断请求,然后按优先级排队,再告诉CPU:"老板,网卡有数据来了,你先处理一下?"

我在项目中遇到过不少新手,上来就写中断服务函数,结果发现中断根本触发不了。查了半天,原来是中断控制器根本没配置好。嗯,这里要记住:中断控制器是中断系统的"交通警察",没有它,所有中断信号就是一锅粥。

中断控制器的核心职责

  • 中断收集:接收来自多个外设的中断信号
  • 中断仲裁:当多个中断同时到达时,决定谁先被处理
  • 中断分发:把中断信号准确送到CPU的某个核(多核处理器下)
  • 中断屏蔽:允许软件临时关闭某些中断,防止被打扰

核心要点:没有中断控制器,CPU就得轮询所有外设,效率极低。中断控制器让CPU从"主动查询"变成了"被动响应",这是嵌入式系统性能提升的关键。

二、GIC(通用中断控制器)介绍

GIC,全称Generic Interrupt Controller,是ARM架构下的事实标准。你想想看,ARM Cortex-A系列处理器几乎都用它。我最早接触GIC是在做一款车机系统时,当时被它的复杂配置搞得头大,但用顺手了就觉得真香。

GIC的版本演进

版本 特点 典型应用
GICv1 最多支持8个CPU核,每个核最多1020个中断 Cortex-A9、A15
GICv2 增加了虚拟化支持,改进了中断路由 Cortex-A53、A72
GICv3 支持更多CPU核(最多256个),引入LPI中断 Cortex-A76、A78
GICv4 增强虚拟化,支持直接注入虚拟中断 最新ARM服务器芯片

我个人习惯在项目初期先确认GIC版本,因为不同版本的寄存器布局差异很大。曾经有个同事拿着GICv2的代码往GICv3的芯片上跑,结果中断全乱套了——这就是血的教训。

GIC的内部结构

GIC主要由两部分组成:分发器(Distributor)CPU接口(CPU Interface)

分发器负责收集所有中断源,管理它们的优先级、使能状态和触发方式。它就像一个总调度台,所有外设的中断线都接到这里。

CPU接口则是每个CPU核独享的通道,负责把中断信号送给对应的CPU核。多核处理器下,每个核都有自己的CPU接口。

实战技巧:在设备树中配置GIC时,interrupt-cells属性决定了每个中断描述需要几个32位单元。GICv2通常用3个单元:中断类型、中断号、触发方式。我曾经因为少写了一个cell,导致中断号解析错误,排查了整整两天。

GIC支持的中断类型

  1. SPI(共享外设中断):多个CPU核都可以处理,比如网卡中断。我在项目中常用SPI来做外设中断。
  2. PPI(私有外设中断):每个CPU核独享的中断,比如每个核的本地定时器。
  3. SGI(软件生成中断):一个CPU核给另一个CPU核发的中断,常用于核间通信。
  4. LPI(本地外设中断):GICv3引入的新类型,基于消息的中断,适合大量中断场景。

避坑指南:我曾经在配置SPI中断时,忘记在设备树中指定interrupt-parent属性,结果内核根本不知道这个中断该交给哪个中断控制器处理。记住:每个使用中断的设备节点,都必须明确指定它的中断父节点。

三、GIC在设备树中的表示

在设备树中,GIC通常这样描述:

gic: interrupt-controller@f0001000 {
    compatible = "arm,gic-400";
    reg = <0x0 0xf0001000 0x0 0x1000>,
          <0x0 0xf0002000 0x0 0x2000>;
    interrupt-controller;
    #interrupt-cells = <3>;
    interrupts = <1 9 0xf04>;
};

我来解释一下关键字段:

  • compatible:告诉内核这是GIC-400控制器
  • reg:两个地址范围,第一个是分发器寄存器,第二个是CPU接口寄存器
  • interrupt-controller:声明这是一个中断控制器节点
  • #interrupt-cells:每个中断描述需要3个cell
  • interrupts:GIC本身也可能需要中断(比如维护中断)

你想想看,设备树就是硬件的"身份证",把GIC的地址、类型、配置都写得清清楚楚。内核启动时,会解析这些信息,然后初始化对应的驱动。

四、GIC的工作流程

我用一个实际场景来说明:假设网卡收到了一个数据包,它拉高了中断线。GIC的工作流程是这样的:

  1. 分发器检测到网卡的中断信号
  2. 分发器查表,找到这个中断的优先级和使能状态
  3. 如果中断使能且优先级足够高,分发器把它发给某个CPU核的接口
  4. CPU接口通知CPU核:有中断来了
  5. CPU核保存当前上下文,跳转到中断向量表
  6. 中断服务函数执行,处理网卡数据
  7. 处理完成后,CPU核写EOI(End of Interrupt)寄存器,告诉GIC我处理完了

关键点:第7步的EOI操作很容易被忽略。我曾经调试一个中断只触发一次的问题,就是因为中断服务函数里忘了写EOI,导致GIC认为中断还在处理中,后续中断都被阻塞了。

五、GIC与GPIO中断的映射

在实际项目中,我们经常需要把GPIO引脚配置成中断模式。这时候,GPIO控制器和GIC之间就需要建立映射关系。

在设备树中,GPIO控制器通常这样描述:

gpio0: gpio@f0000000 {
    compatible = "my-gpio-controller";
    reg = <0x0 0xf0000000 0x0 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    interrupts = <0 32 4>;  // 连接到GIC的SPI 32号中断
};

这里的关键是interrupts属性,它告诉内核:这个GPIO控制器的中断输出,接到了GIC的哪个中断号上。当GPIO引脚检测到电平变化时,GPIO控制器会向GIC发中断,GIC再通知CPU。

实战经验:我在做一款智能门锁时,需要把按键的GPIO配置成中断触发。当时在设备树里配了interrupts = <0 64 1>,结果按键按下去没反应。后来发现是interrupt-parent没写对,GPIO控制器不知道它的中断该发给谁。加上interrupt-parent = <&gic>后,问题就解决了。

六、总结

中断控制器是嵌入式系统的"神经系统",GIC则是ARM架构下的标准方案。理解GIC的结构、中断类型和设备树配置,是每个嵌入式工程师的必修课。

我个人建议初学者先从GICv2入手,因为它的寄存器相对简单,文档也最全。等把GICv2玩熟了,再去看GICv3的LPI和虚拟化特性,会轻松很多。

记住:中断配置无小事。一个中断号写错、一个cell少写、一个EOI忘记,都可能导致系统崩溃。我在项目里吃过太多亏了,希望大家能少走弯路。

GIC中断处理流程 外设(网卡) 中断请求 GIC分发器 (Distributor) 优先级仲裁·使能控制 路由选择 CPU接口 (CPU Interface) 每个核独享 IRQ信号 CPU核 (Cortex-A系列) 执行中断服务函数 EOI写回 中断类型 • SPI:共享外设中断 • PPI:私有外设中断 • SGI:软件生成中断 • LPI:本地外设中断 关键寄存器: • ICDDCR:分发器控制 • ICDISER:中断使能 • ICDIPR:中断优先级 • ICCEOIR:EOI寄存器 ⚠ 别忘了写EOI! ⚠ 检查interrupt-parent ⚠ 确认GIC版本 图:GIC中断处理流程与中断类型说明
专注资料整理