【STM32】中断软件分支处理( NVIC 和 GIC)
中断软件分支处理、NVIC 和 GIC 是嵌入式系统和操作系统内核中非常关键的内容,尤其在 Cortex-M
和 Cortex-A
架构中有很大差别。本篇博客将分别介绍三者之间的关系。这部分内容涉及ARM 架构中断系统的底层机制,也是理解嵌入式系统、操作系统内核、驱动开发的关键知识。所以在我们接下来的文章向下一个阶段推进之前,我必须以系统化的方式向您详细介绍这三者的本质、区别与联系。
一、什么是中断软件分支处理?
中断软件分支(Software Interrupt Vectoring)的定义:
CPU 收到中断信号后,不通过硬件自动跳转,而是通过软件跳转到对应中断服务函数的机制。
详细点说:
中断发生后,CPU 不直接跳转到某个固定地址处理函数,而是进入统一入口,由软件读取中断号,再“查表跳转”到具体中断服务函数(ISR) 的机制。
一些前置概念先导:
中断 CPU 在执行程序时,被外部或内部事件打断,转去执行专门处理函数的机制
中断控制器(IC) 硬件模块,管理多个中断源,决定中断优先级、分发给哪个 CPU
中断向量表 存储中断处理函数地址的表,CPU 通过它跳转到对应中断处理逻辑
软件分支处理 CPU 进入统一异常入口后,由软件识别中断号并跳转到对应处理函数
中断软件分支详细示意流程:
graph TDA[外设产生中断] --> B[NVIC or GIC]B --> |Cortex-M| C[NVIC 处理]C --> D[CPU 跳转向量表]D --> E[ISR 执行]B --> |Cortex-A| F[GIC 分发]F --> G[CPU 进入统一入口]G --> H[读取中断号]H --> I[软件查表跳转 ISR]
这种方式便于用户空间自定义中断行为,常用于高级操作系统如 Linux
。
中断处理有两种方式:
中断控制器(IC):中断控制器是 CPU 的“中断信号调度中心”。它决定了:哪个中断优先响应?哪个 CPU 核来处理?中断号是多少?而这时候便出现了两大主流:1、NVIC(Cortex-M 内建);2、GIC(Cortex-A 外设)。
- ✅ 硬件向量分支(如 Cortex-M)
- CPU 自动从 中断号 × 4 + 向量基地址 中读取跳转地址
- 由硬件完成中断分发和跳转
- 使用 NVIC(Nested Vectored Interrupt Controller)
- ✅ 软件分支派发(如 Cortex-A)
- 所有中断统一进入一个入口地址(如
__irq_svc
) - 由软件读取中断号(从中断控制器),通过查表跳转到对应 ISR
- 使用 GIC(Generic Interrupt Controller)
- 所有中断统一进入一个入口地址(如
中断控制器架构分层模型
+-----------------------------+
| 外设设备(GPIO/UART)|
+-----------------------------+│▼
+-----------------------------+
| 中断控制器(NVIC / GIC) |
| - 优先级控制 |
| - 中断屏蔽 |
| - 中断号分配 |
| - 目标 CPU 分发(GIC) |
+-----------------------------+│▼
+-----------------------------+
| CPU 异常入口(向量表) |
| - NVIC:自动跳转 |
| - GIC:统一入口 + 软件分派 |
+-----------------------------+│▼
+-----------------------------+
| 中断服务函数(ISR) |
+-----------------------------+
二、NVIC(Nested Vectored Interrupt Controller)
适用于 ARM Cortex-M 系列(如 STM32) 的中断控制器。
✅ NVIC 特点:
内嵌在 Cortex-M 内核中,处理速度快
每个中断有唯一入口地址(ISR),在向量表中定义
自动保存/恢复上下文(部分寄存器)
特性 | 说明 |
---|---|
内置于 Cortex-M 内核 | 无需外部中断控制器 |
支持中断嵌套 | 优先级分组机制 |
编号由芯片厂商定义 | 通常 IRQn_Type 枚举 |
中断入口固定 | 硬件自动跳转,无需软件分发 |
优先级控制 | 支持抢占优先级和响应优先级 |
✅ NVIC 相关函数(CMSIS):
NVIC_EnableIRQ(IRQn); // 开启中断
NVIC_DisableIRQ(IRQn); // 关闭中断
NVIC_SetPriority(IRQn, prio); // 设置优先级
✅ 向量表结构(以 STM32 为例):
const void* vector_table[] __attribute__((section(".isr_vector"))) = {(void*) &__StackTop,Reset_Handler,NMI_Handler,HardFault_Handler,...TIM2_IRQHandler, // IRQ #28
};
📘 NVIC 示例:STM32 定时器中断
// 向量表中注册 ISR
void TIM2_IRQHandler(void) {HAL_TIM_IRQHandler(&htim2);
}// NVIC 设置优先级 & 使能中断
NVIC_SetPriority(TIM2_IRQn, 1);
NVIC_EnableIRQ(TIM2_IRQn);
✅ NVIC 中断流程图(硬件向量跳转):
外设产生中断 (如 TIM2)↓
NVIC 确认中断未屏蔽 → 比较优先级↓
CPU 自动跳转到向量表对应地址↓
执行 ISR(例如 TIM2_IRQHandler)↓
中断返回 → 恢复主流程
三、GIC(Generic Interrupt Controller)
适用于 ARM Cortex-A 系列(如 A7、A53、A55) 的中断控制器,主要用于运行 Linux、Android 的多核系统。
✅ GIC 架构组成(通常为 GICv2/v3):
组件 | 说明 |
---|---|
Distributor(GICD) | 接收中断请求,决定目标 CPU |
CPU Interface(GICC) | 每个核对应的本地接口,控制本地中断处理 |
SGI | 软件生成中断 |
PPI | 私有外设中断(每核独享) |
SPI | 共享外设中断(所有核共享) |
中断类型:
类型 | 编号范围 | 说明 |
---|---|---|
SGI (软件中断) | 0–15 | 用于核间通信 |
PPI (私有中断) | 16–31 | 针对当前核 |
SPI (共享中断) | 32–1019 | 外设中断,多个核共享 |
GIC 配置步骤(简化流程):
1. 读取中断号 GICC_IAR
2. 查表跳转 ISR(软件分支)
3. 执行中断服务函数
4. 写 GICC_EOIR 通知中断处理完毕
Linux 中的中断处理就是基于此机制:irq_desc[] → handle_irq()
✅ 中断流程(GIC + Linux):
- 外设产生中断 → GICD 接收
- GIC 判断目标 CPU → 投递中断
- CPU 接收中断 → 进入
__irq_svc
- 内核调用
gic_read_iar()
获取中断号 - 查找中断描述符
irq_desc[]
- 调用对应 ISR(驱动注册的 handler)
1. __irq_svc: 汇编级中断入口
2. → arch_irq_handler_default()
3. → gic_handle_irq()
4. → read GICC_IAR → 得到中断号
5. → generic_handle_irq(irq)
6. → irq_desc[irq].handle_irq()
7. → ISR(驱动注册的中断处理函数)
8. → write GICC_EOIR 表示中断结束
在 Linux 内核中:
// Linux 中断通用框架
__irq_svc: // 异常向量入口-> entry.S-> irq_handler-> gic_handle_irq()-> generic_handle_irq()-> irq_desc[irq].handle_irq()-> 驱动注册的 ISR
即:Linux 通过统一的中断入口 gic_handle_irq()
获取中断号 → 查表 → 跳转到目标 驱动注册的中断处理函数。
✅ GIC 中断流程图(软件分派):
外设产生中断(如 UART1)↓
GIC Distributor 接收中断请求↓
确定目标 CPU(如 CPU0)↓
CPU0 收到中断 → 跳转到统一入口地址(__irq_svc)↓
软件读取中断号(GICC_IAR)↓
调用 irq_desc[irq].handler → 驱动注册函数↓
处理中断逻辑 → 写 GICC_EOIR 结束↓
返回用户态
GIC 架构图(简化)
+----------------------+
| GIC Distributor |
| - 接收中断 |
| - 目标 CPU 核 |
| - SPIs(共享中断) |
+----------------------+|
+--------+--------+
| CPU Interface 0 |
| CPU Interface 1 |
| ... |
+-----------------+每个 CPU 核 ↔ GICC ↔ GICD ↔ 外设
📘 GIC 示例:Linux 驱动中注册中断
// 设备驱动中注册 IRQ
int irq = platform_get_irq(pdev, 0);
request_irq(irq, my_irq_handler, 0, "my_device", NULL);irqreturn_t my_irq_handler(int irq, void *dev) {printk("Interrupt received!\n");return IRQ_HANDLED;
}
硬件向量(NVIC)vs 软件分支(GIC)对比
对比项 | NVIC(Cortex-M) | GIC(Cortex-A) |
---|---|---|
适用架构 | Cortex-M 系列 | Cortex-A 系列 |
中断数量 | 一般 < 256 | 可达 1020 个 |
工作模式 | (CPU 自动跳转 ISR)硬件向量跳转 | (需要代码处理)软件跳转(中断分发) 统一入口 → 查表 |
响应速度 | 更快 | 稍慢但灵活 |
多核支持 | 不支持 | 支持分发到不同 CPU 核 |
中断类型 | 外设中断 | SGI / PPI / SPI |
驱动复杂度 | 低 | 高,需内核支持 |
常用系统 | 裸机开发、RTOS | Linux、Android 系统 |
那么,从发者视角出发:何时使用哪种?
开发场景 | 使用方式 | 控制器 |
---|---|---|
STM32 裸机 / FreeRTOS | NVIC + 硬件向量表 | NVIC |
Cortex-A + Linux | GIC + 软件中断派发 | GIC |
多核系统 | GIC 分发到指定 CPU | GIC |
安全系统(TrustZone) | GIC 支持 Secure/Non-Secure 中断 | GICv2/3/4 |
通过对比可见:
- NVIC 适用于单核 MCU,如 STM32、GD32
- GIC 适用于多核 SoC,如 RK3568、i.MX6、A7
- 软件分派方式是 Linux 内核中断的核心机制
NVIC 是硬件自动跳转中断处理函数;GIC 需要软件读取中断号后手动跳转。中断软件分派是 GIC 的核心特征,NVIC 则通过硬件向量表处理。
综上,
NVIC 是硬件帮你跳转,GIC 是你自己跳转。中断软件分支是“跳转方式的选择”,NVIC 和 GIC 是“分发机制的差异”。
Cortex-M 使用 NVIC,靠硬件向量跳转;Cortex-A 使用 GIC,依赖软件中断分派。
在后续的学习路径中,不同阶段需要掌握的内容有:
阶段 内容 建议平台
初级 NVIC、CMSIS 中断函数 STM32 / GD32
提高 GIC 配置、SGI 使用 Cortex-A7 / RK3568
系统级 Linux 中断子系统源码分析 STM32MP157 / IMX6ULL
驱动级 request_irq / 中断注册机制 Linux 驱动开发
熟练掌握和运用 中断机制是成为驱动工程师的基础,所以进入Linux驱动前要先介绍这部分核心的内容。
以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!