Nuttx - Interrupt
从 GIC 硬件中断触发到 ISR 执行再到中断级上下文切换,完整解析 NuttX 如何在 ARMv7-A 上管理中断的注册、分发、响应与任务调度联动。
本文回答以下问题:硬件中断触发后 CPU 经历了哪些步骤才到达你注册的 ISR?GIC 如何决定哪个中断优先?为什么 ISR 中调用 sem_post() 不会立即切换任务而要等中断返回?enter_critical_section() 到底做了什么?读完后,你将能够理解中断从硬件到调度器的完整链路,并具备独立编写和调试 NuttX 中断驱动的能力。
1. 开篇:中断处理框架解决什么问题?
中断是嵌入式系统的”神经系统”——外设通过中断通知 CPU”我有数据了”或”我完成了”。但硬件只负责”打断 CPU 并跳转到固定地址”,其余所有问题都需要软件框架来解决:
- 识别:哪个设备产生了中断?(几十上百个中断源共享少量 IRQ 线)
- 分发:找到该中断对应的处理函数(ISR)
- 保护:ISR 执行期间如何防止数据竞争?
- 调度联动:ISR 唤醒了高优先级任务后,如何最快切换过去?
NuttX 的中断处理框架围绕 ARMv7-A 的 GIC(Generic Interrupt Controller)硬件构建,形成了一条从硬件触发到任务切换的完整链路。NuttX 官方文档(Documentation/implementation/interrupt_controls.rst)描述了中断控制的设计意图:中断处理应当尽可能短,复杂逻辑应交给任务上下文完成。
接下来先了解 GIC 硬件的基本架构——它是整个中断框架的物理基础。
2. GIC 硬件基础
ARMv7-A 处理器只有两条中断输入线:IRQ 和 FIQ。但一个 SoC 可能有上百个中断源(UART、SPI、DMA、定时器…)。GIC 负责将这些中断源汇聚、排优先级、路由到正确的 CPU 核心,最终拉高 CPU 的 IRQ 线。
2.1 GIC 的两个组件
1 | +------------------+ +------------------+ |
Distributor 管理所有 SPI 中断的使能、优先级和目标 CPU 路由;CPU Interface 负责优先级过滤、中断确认(ACK)和结束信号(EOI)。
- Distributor(分发器):管理所有中断源的使能、优先级、目标 CPU 路由。系统全局唯一。
- CPU Interface(CPU 接口):每个核心一个。负责优先级屏蔽、中断确认(ACK)、中断结束(EOI)信号。
这两个组件是真实的硬件模块,不是软件抽象。 GIC 是 ARM 公司定义的硬件 IP(规范见 ARM Generic Interrupt Controller Architecture Specification v2.0),SoC 厂商在集成 Cortex-A7/A9/A15 等 ARMv7-A 核心时会将 GIC 作为片上外设一并流片。Distributor 和 CPU Interface 各自拥有独立的 memory-mapped 寄存器空间:Distributor 寄存器组(GICD_*)映射在
MPCORE_ICD_BASE,CPU Interface 寄存器组(GICC_*)映射在MPCORE_ICC_BASE。NuttX 代码中的putreg32(val, GIC_ICDDCR)/getreg32(GIC_ICCIAR)就是通过 MMIO 直接访问这些硬件寄存器。
2.2 三类中断
文件:arch/arm/src/armv7-a/gic.h:598-651
| 类型 | ID 范围 | 特征 | 用途 |
|---|---|---|---|
| SGI (Software Generated) | 0–15 | 软件触发,per-CPU 私有 | SMP 跨核通知(启动、调度、函数调用) |
| PPI (Private Peripheral) | 16–31 | 硬件触发,per-CPU 私有 | 每核私有定时器、watchdog |
| SPI (Shared Peripheral) | 32+ | 硬件触发,可路由到任意 CPU | UART、SPI、DMA 等外设中断 |
实例:qemu-armv7a 上的典型中断分配
1 | ID 27: Global Timer (PPI) |
2.3 关键寄存器
后续所有代码都是对 GIC 寄存器的读写操作。这里先建立一张”寄存器速查表”,在阅读第 3-5 章时可随时回查。
文件:arch/arm/src/armv7-a/gic.h:107-277
Distributor 寄存器(基址 MPCORE_ICD_VBASE,系统唯一)
| NuttX 宏 | 偏移 | 全称 | 作用 | 后文使用场景 |
|---|---|---|---|---|
GIC_ICDDCR |
0x000 | Distributor Control Register | bit[0]=1 使能 Group0 转发,bit[1]=1 使能 Group1 转发。写 0 则所有中断被 Distributor 截停 | 第 3 章初始化最后一步 |
GIC_ICDISER(n) |
0x100+ | Interrupt Set-Enable Register | 写 1 使能对应中断(每 bit 对应一个 IRQ ID)。32 个中断一组 | up_enable_irq() |
GIC_ICDICER(n) |
0x180+ | Interrupt Clear-Enable Register | 写 1 禁用对应中断。与 ICDISER 镜像,分开设计避免读-改-写竞争 | 第 3 章初始化时禁用所有 SPI |
GIC_ICDIPR(n) |
0x400+ | Interrupt Priority Register | 每个中断 8-bit 优先级(0=最高,255=最低)。4 个中断打包在一个 32-bit 寄存器中 | 第 3 章设置默认优先级 128 |
GIC_ICDIPTR(n) |
0x800+ | Interrupt Processor Targets Register | 每个中断 8-bit CPU 掩码(bit0=CPU0, bit1=CPU1…)。决定中断路由到哪个核 | 第 3 章路由所有 SPI 到 CPU0 |
GIC_ICDICFR(n) |
0xC00+ | Interrupt Configuration Register | 每中断 2-bit:01=电平触发 1-N 模型,11=边沿触发 |
第 3 章设置 SPI 为电平触发 |
Group 0 与 Group 1
上表中 GIC_ICDDCR 提到了 Group0 和 Group1——这是 GIC 对中断的一种安全分组机制。每个中断 ID 通过 Distributor 的 GIC_ICDGRPR(n) 寄存器被分配到其中一组:
| 分组 | 信号路径 | 典型用途 |
|---|---|---|
| Group 0 | FIQ(快速中断) | Secure 世界的中断(TrustZone 安全侧),如安全定时器、安全看门狗 |
| Group 1 | IRQ(普通中断) | Non-secure 世界的中断,即操作系统和应用使用的常规中断 |
ARM TrustZone 通过这种分组实现安全隔离:Secure 世界的中断走 FIQ,Non-secure 走 IRQ,两者互不干扰。GIC_ICDDCR 的两个 bit 分别控制这两组的总开关。
NuttX 的处理: NuttX 不使用 TrustZone 安全扩展,初始化时同时使能两组(GIC_ICDDCR_ENABLEGRP0 | GIC_ICDDCR_ENABLEGRP1),并通过 GIC_ICCICR 中的 FIQEN 位控制 Group 0 是否走 FIQ。在 qemu-armv7a 上,NuttX 将所有中断统一作为 IRQ 处理,Group 分组对功能没有实际影响——但寄存器中这两个 bit 必须都置 1,否则会有中断无法到达 CPU。
CPU Interface 寄存器(基址 MPCORE_ICC_VBASE,每核一份)
| NuttX 宏 | 偏移 | 全称 | 作用 | 后文使用场景 |
|---|---|---|---|---|
GIC_ICCICR |
0x00 | CPU Interface Control Register | 使能/禁用该核的中断接收。Group0/Group1 分别控制 | 第 3 章使能 CPU Interface |
GIC_ICCPMR |
0x04 | Priority Mask Register | 优先级阈值。只有优先级数值小于此值的中断才能通过(值越小优先级越高)。写 0xFF = 放行所有 | 第 3 章设置为 0xFF(全放行) |
GIC_ICCBPR |
0x08 | Binary Point Register | 决定优先级的哪些位参与”抢占比较”。值 = 7 表示没有任何位用于抢占——即禁用中断嵌套 | 第 3 章禁用嵌套 |
GIC_ICCIAR |
0x0C | Interrupt Acknowledge Register | 读取此寄存器有两个副作用:(1) 返回当前最高优先级 Pending 中断的 ID [9:0];(2) 将该中断状态从 Pending → Active | 第 5 章 arm_decodeirq() |
GIC_ICCEOIR |
0x10 | End of Interrupt Register | 写入中断 ID,通知 GIC 该中断处理完毕。中断状态从 Active → Inactive,GIC 可以再次触发 | 第 5 章中断处理完成后 |
寄存器地址计算
NuttX 中这些寄存器的实际地址 = 基址 + 偏移。以 GIC_ICCIAR 为例:
1 | /* gic.h:110 */ |
MPCORE_ICC_VBASE 由板级硬件决定(qemu-armv7a 上为 GIC 外设映射的虚拟地址)。代码中 getreg32(GIC_ICCIAR) 就是一次 32-bit MMIO 读操作。
ICCIAR 寄存器位域
1 | 31 13 12 10 9 0 |
- **Interrupt ID [9:0]**:触发中断的 IRQ 编号(0-1019)。值 1023(0x3FF)表示”伪中断”(Spurious),即没有有效的 Pending 中断。
- **CPUSRC [12:10]**:对于 SGI,指示是哪个 CPU 发送的。对于 SPI/PPI 此字段无意义。
了解了 GIC 硬件结构和寄存器含义,下面看 NuttX 如何在启动时配置它们。
3. GIC 初始化
GIC 上电后所有中断默认禁用、优先级未设置、路由未配置。不初始化就收不到任何中断。NuttX 在启动早期(nx_start() → up_irqinitialize() → arm_gic_initialize())完成 GIC 配置。
3.1 CPU0 专属初始化:arm_gic0_initialize()
文件:arch/arm/src/armv7-a/arm_gicv2.c:165-225(关键摘录)
1 | void arm_gic0_initialize(void) |
只由 CPU0 在启动时执行一次,配置系统级的 SPI 中断。
3.2 每 CPU 初始化:arm_gic_initialize()
文件:arch/arm/src/armv7-a/arm_gicv2.c:241-378(关键摘录)
1 | void arm_gic_initialize(void) |
关键配置:中断嵌套默认禁用。GIC_ICCBPR_NOPREMPT 设置 Binary Point = 7,意味着 GIC 不会用优先级的任何位来做抢占比较——当一个中断正在处理时,即使更高优先级的中断到来也不会嵌套进入。
对比 FreeRTOS:FreeRTOS 在 Cortex-M 上通过 BASEPRI 寄存器允许高优先级中断嵌套。NuttX 在 ARMv7-A 上默认不嵌套——简化了 ISR 编写(不用考虑重入),代价是高优先级中断可能被低优先级中断延迟。
GIC 初始化完成后,中断可以触发了。下面看 CPU 收到 IRQ 后的第一步:汇编向量入口。
4. IRQ 向量入口:arm_vectorirq
当 IRQ 触发时,CPU 硬件只做三件事:保存 CPSR 到 SPSR_irq、保存返回地址到 LR_irq、跳转到 IRQ 向量地址。所有寄存器保存、栈切换、C 函数调用都必须由汇编代码完成。
4.1 完整汇编流程
文件:arch/arm/src/armv7-a/arm_vectors.S:171-278(关键摘录,完整函数约 107 行)
1 | arm_vectorirq: |
下图展示了压栈阶段(Step 1-6)构建的完整寄存器帧,以及出栈阶段(Step 9-10)的恢复过程。底部还展示了上下文切换场景——arm_decodeirq() 返回不同任务的 regs 指针时,Step 10 会恢复另一个任务的寄存器,实现透明的任务切换:

与 SVC 入口(arm_vectorsvc)的对比:
| 特征 | arm_vectorirq (IRQ) | arm_vectorsvc (SVC) |
|---|---|---|
| 返回地址修正 | sub lr, #4(必须) |
不需要 |
| 中断栈 | 切换到独立中断栈 | 不切换(用 SYS 栈) |
| 返回值含义 | 可能指向不同任务的帧 | 同上 |
| 触发来源 | 硬件外设 | 软件 SVC #0 指令 |
关键洞察:arm_decodeirq() 的返回值 r0 可能指向一个不同任务的寄存器帧。如果 ISR 唤醒了更高优先级的任务,arm_doirq() 会返回新任务的 xcp.regs——汇编代码从这个新帧恢复寄存器,于是 CPU “返回”到了新任务,完成了中断级上下文切换。
4.2 关键观察
回顾整个 arm_vectorirq 流程,有两点核心设计值得特别注意:
观察 1:模式切换路径是 IRQ → SYS → IRQ
中断入口在 IRQ 模式下只停留数条指令(修正 LR、保存 banked 寄存器到内存),随即切到 SYS 模式完成所有实质工作(压栈、执行 C 分发函数),最后切回 IRQ 模式做异常返回。这样设计的原因:
- SYS 模式的 SP 就是当前任务的栈指针——上下文帧保存在任务私有栈上,天然支持上下文切换(切换任务 = 切换帧指针)。
- 提前离开 IRQ 模式保护了 LR_irq 和 SPSR_irq 这两个 banked 寄存器——它们只有一份,停留在 IRQ 模式期间如果发生异常会被覆盖。
- 返回时必须切回 IRQ 模式,因为
ldmia {r13,r14}^(^后缀写入 USR 寄存器)和rfeia(异常返回)都要求在特权异常模式下执行。
观察 2:C 分发函数运行在公共中断栈 g_intstacktop 上
压栈完成后,SP 被 setirqstack 切换到全局中断栈(大小由 CONFIG_ARCH_INTERRUPTSTACK 决定,per-core 一份)。arm_decodeirq() 及其调用的所有 ISR 都在这个公共栈上执行,不消耗任何任务栈空间。任务栈只承载上下文帧本身(固定大小),不随 ISR 复杂度增长。这意味着:
- 任务栈规划时无需考虑”最坏情况下 ISR 嵌套深度”——ISR 的栈开销被隔离到中断栈。
- 所有核共享中断栈(per-core),100 个任务也只有 1 份中断栈开销。
- 代价:中断栈大小必须覆盖最深的 ISR 调用链,如果设置不当会栈溢出(但影响范围是全系统而非单个任务)。
下面看 arm_decodeirq() 如何从 GIC 读取中断号并分发。
5. arm_decodeirq():从 GIC 读取中断号
CPU 只知道”有 IRQ 发生了”,但不知道是哪个设备产生的。必须读 GIC 的 ICCIAR 寄存器才能获取中断 ID。
5.1 实现
文件:arch/arm/src/armv7-a/arm_gicv2.c:395-429
1 | uint32_t *arm_decodeirq(uint32_t *regs) |
读 GIC_ICCIAR 的副作用:这不仅仅是”读取”——GIC 会将该中断标记为 Active 状态(从 Pending → Active),并且如果有更低优先级的中断排队,它们会被阻塞直到 EOI。
写 GIC_ICCEOIR 的作用:告诉 GIC”我处理完了”。中断状态从 Active → Inactive,GIC 可以再次触发该中断(如果仍有 Pending)。
arm_decodeirq() 拿到中断号后调用 arm_doirq()——这是中断处理的核心 C 函数。
6. arm_doirq():中断处理与上下文切换检测
ISR 运行在中断上下文中——它使用的是中断栈(或被中断任务的栈),不在任何任务的正常执行流中。如果 ISR 中直接做上下文切换,中断返回的寄存器帧会混乱。NuttX 的策略是:ISR 只修改调度器状态(就绪队列),实际切换延迟到中断返回时。
6.1 实现
文件:arch/arm/src/armv7-a/arm_doirq.c:56-129(关键摘录)
1 | uint32_t *arm_doirq(int irq, uint32_t *regs) |
6.2 上下文切换检测的工作原理(选读)
这是 NuttX 中断处理最精妙的设计。让我用一个场景走一遍:
1 | 时刻 T0: TaskA(prio=100) 正在运行,定时器中断触发 |
一句话总结:ISR 只管修改调度器状态(把 TaskB 放到链表头),arm_doirq() 通过比较寄存器帧指针检测变化,汇编代码根据返回的帧指针”返回”到不同的任务——无需显式调用任何切换函数。
上面展示了 ISR 直接修改调度器状态的场景。但复杂驱动(如网卡、SPI 设备)的中断处理不适合全部在中断上下文完成——NuttX 提供了 work_queue 机制来延迟处理。
7. 延迟处理:work_queue(NuttX 的”下半部”)
ISR 运行在中断上下文中,有多个限制:
- 中断栈有限(通常 2-4KB),不能有深层调用
- 不能阻塞(不能调用
sem_wait()、sleep()等) - 会延迟其他中断(NuttX 默认不嵌套,ISR 期间其他 IRQ 被屏蔽)
对于需要做 SPI/I2C 通信、大量数据处理或网络协议栈调用的中断,应在 ISR 中仅做最小工作(ACK 硬件 + 禁用中断),然后将实际处理交给内核工作线程。
7.1 work_queue API
文件:include/nuttx/wqueue.h:406
1 | int work_queue(int qid, FAR struct work_s *work, worker_t worker, |
qid:HPWORK(高优先级工作线程)或LPWORK(低优先级工作线程)work:工作项结构体(通常嵌入在驱动私有数据中)worker:延迟执行的函数(签名:void worker(FAR void *arg))arg:传递给 worker 的参数delay:延迟多少 tick 后执行(0 = 立即排队)
7.2 HPWORK 与 LPWORK
| 工作队列 | 配置项 | 默认优先级 | 特征 |
|---|---|---|---|
HPWORK |
CONFIG_SCHED_HPWORK |
224(接近最高) | 不应做阻塞操作,用于 ISR 延迟处理 |
LPWORK |
CONFIG_SCHED_LPWORK |
50(较低) | 可做较长时间操作,用于非紧急后台任务 |
两者都是普通的内核线程——它们在任务上下文中运行,有自己的栈,可以做大多数内核操作(但 HPWORK 优先级高,不应长时间阻塞)。
7.3 实例:ICJx IO Expander 驱动的中断处理
文件:drivers/ioexpander/icjx.c:1081-1094(ISR)
1 | static int icjx_interrupt(int irq, FAR void *context, FAR void *arg) |
文件:drivers/ioexpander/icjx.c:1013-1038(Worker,任务上下文)
1 | static void icjx_interrupt_worker(void *arg) |
模式总结:
1 | ISR (interrupt context): Worker (task context, HPWORK): |
7.4 与 Linux 下半部机制的对比
| 机制 | Linux | NuttX 等价物 |
|---|---|---|
| softirq | 内核软中断,不可阻塞,per-CPU | 无 |
| tasklet | 基于 softirq,不可阻塞 | 无 |
| workqueue | 内核线程,可阻塞 | work_queue(HPWORK/LPWORK, ...) ← 唯一选项 |
| threaded IRQ | 中断线程化,可阻塞 | HPWORK 工作线程(等效) |
NuttX 用一个统一的 work_queue 机制覆盖了 Linux 需要多种机制处理的场景。设计哲学:嵌入式系统中断源少、处理逻辑相对简单,一个高优先级工作线程足够应对。
work_queue 解决了”ISR 太长”的问题。下面回到 ISR 注册本身——看 irq_attach() 如何将 ISR 绑定到中断号。
8. irq_attach() 与 irq_dispatch():ISR 注册与分发
8.0 为什么需要软件分发?——ARMv7-A 的向量表限制
在理解 irq_attach() / irq_dispatch() 之前,需要先了解一个硬件事实:ARMv7-A 的异常向量表只有 8 个条目,所有外设中断共用其中一个入口。
1 | ARMv7-A Exception Vector Table (仅 8 项): |
无论是 UART、定时器、DMA 还是 SPI 触发中断,CPU 都跳到偏移 0x18 这同一个地址(即 arm_vectorirq)。硬件不会告诉你”是哪个设备”——必须读 GIC_ICCIAR 获取中断号,再通过软件查表找到对应的处理函数。
对比 Cortex-M(NVIC): Cortex-M 的向量表有 16 + N 个条目(N = 外设中断数,可达数百个),每个外设中断有独立的入口地址。IRQ 33 触发时,CPU 硬件直接跳到向量表第 33 项指向的函数——不需要软件分发。这就是为什么 FreeRTOS 在 Cortex-M 上可以直接用硬件向量表注册 ISR,而 NuttX 在 ARMv7-A 上必须维护一张软件分发表。
1 | Cortex-M (NVIC): ARMv7-A (GIC): |
理解了这个硬件背景,就能明白为什么 NuttX 需要 g_irqvector[] 数组 + irq_attach() 注册机制——它们本质上是用软件实现了 ARMv7-A 硬件缺失的”per-interrupt 向量表”。
8.1 处理函数表
文件:sched/irq/irq.h:50-59
1 | struct irq_info_s |
文件:sched/irq/irq_initialize.c:53-57
1 | struct irq_info_s g_irqvector[NR_IRQS]; |
启动时所有条目初始化为 irq_unexpected_isr(未注册中断的 panic 处理)。
8.2 irq_attach():注册 ISR
文件:sched/irq/irq_attach.c:114-185
1 | int irq_attach(int irq, xcpt_t isr, FAR void *arg) |
ISR 函数签名:int handler(int irq, FAR void *context, FAR void *arg)
irq:中断号context:寄存器帧指针(通常不用)arg:irq_attach()时传入的自定义参数
8.3 irq_dispatch():查表调用
文件:sched/irq/irq_dispatch.c:98-165
1 | void irq_dispatch(int irq, FAR void *context) |
整个分发过程就是一次数组索引 + 函数指针调用——O(1) 复杂度。
9. enter_critical_section():临界区保护
多任务系统中,共享数据可能被”任务 + 中断”或”多个 CPU”同时访问。enter_critical_section() 通过禁用中断(+ SMP 下的 spinlock)保证代码段的原子性。
9.1 ARMv7-A 实现(非 SMP)
文件:arch/arm/include/armv7-a/irq.h:367-380
1 | static inline irqstate_t up_irq_save(void) |
enter_critical_section() = up_irq_save()(禁中断)+ 记录嵌套计数。leave_critical_section() = 减计数 + up_irq_restore()(恢复中断状态)。
9.2 与 sched_lock() 的区别
| 特性 | enter_critical_section() | sched_lock() |
|---|---|---|
| 机制 | 禁用 CPU 中断(CPSR.I) | 递增 lockcount 计数器 |
| 中断响应 | 被屏蔽 | 正常响应 |
| 保护范围 | 防止中断 + 防止切换 | 仅防止切换 |
| SMP 行为 | + 全局 spinlock | 仅本 CPU 有效 |
| 延迟影响 | 增加中断响应延迟 | 不影响中断响应 |
| 适用场景 | 极短临界区(几条指令) | 较长临界区(允许中断响应) |
原则:能用 sched_lock() 就不用 enter_critical_section()——前者对实时性的影响更小。
10. 中断栈
如果中断使用被中断任务的栈,每个任务都必须预留足够空间给中断处理(ISR + 可能的嵌套)。这浪费内存。独立中断栈让所有中断共享一块固定大小的栈空间,任务栈只需考虑自身需求。
10.1 配置与分配
文件:arch/arm/src/armv7-a/arm_vectors.S:796-828
1 | #if !defined(CONFIG_SMP) && CONFIG_ARCH_INTERRUPTSTACK > 7 |
在 BSS 段分配固定大小的中断栈。arm_vectorirq 入口处通过 setirqstack 宏将 SP 切换到 g_intstacktop。
配置值的含义:
CONFIG_ARCH_INTERRUPTSTACK = 0:不使用独立中断栈(中断直接用任务栈)CONFIG_ARCH_INTERRUPTSTACK = 4096:分配 4KB 中断栈
实践建议:对于 ARMv7-A KERNEL 模式,推荐设置 CONFIG_ARCH_INTERRUPTSTACK >= 2048——因为 ISR 中可能调用较深的内核函数(如 sem_post() → nxsched_add_readytorun())。
11. 完整中断处理流程总览
下图展示了从硬件中断触发到上下文切换完成的完整时序:

将上面所有环节串起来:
1 | Hardware IRQ asserted |
12. 对比分析
| 特性 | NuttX (ARMv7-A GICv2) | Linux (ARMv7-A GICv2) | FreeRTOS (Cortex-M NVIC) |
|---|---|---|---|
| 中断控制器 | GIC,软件配置 | 同,+ irqdomain 抽象 | NVIC,硬件优先级 |
| 中断嵌套 | 默认禁用 | 支持(threaded IRQ) | 硬件自动嵌套 |
| ISR 注册 | irq_attach(irq, handler, arg) |
request_irq(irq, handler, flags, name, dev) |
直接写向量表 |
| ISR 上下文 | 中断栈或任务栈 | 专用内核栈 | 任务栈(MSP 或 PSP) |
| 上下文切换 | 中断返回时比较 regs 指针 | schedule() 在 softirq/preempt 点 |
PendSV(延迟切换) |
| 下半部机制 | work_queue (HPWORK/LPWORK) | softirq / tasklet / workqueue | 无(ISR 直接处理) |
| EOI 时机 | ISR 返回后写 EOI | 同 | 硬件自动(NVIC) |
| ISR 函数签名 | int f(int irq, void *ctx, void *arg) |
irqreturn_t f(int irq, void *dev_id) |
void f(void) |
NuttX 的设计取舍:
- 无下半部——ISR 中直接完成所有工作,简单但 ISR 不能太长
- 默认不嵌套——避免了复杂的重入问题,但可能增加高优先级中断的延迟
- 中断级上下文切换——比 FreeRTOS 的 PendSV 更直接(不需要额外的软中断),但实现复杂度在汇编层
13. 关键要点
**GIC 是 ARMv7-A 中断的”总管”**——管理几百个中断源的使能、优先级和 CPU 路由。NuttX 默认禁用中断嵌套。
arm_vectorirq汇编入口做三件事:保存寄存器到 SYS 栈帧 → 切换到中断栈 → 调用 C 函数arm_decodeirq()。中断号从 GIC_ICCIAR 寄存器读取(
arm_decodeirq()),读操作本身就是 ACK;处理完后写 GIC_ICCEOIR 表示 EOI。**
arm_doirq()的核心逻辑是”比较帧指针”**——ISR 运行前保存regs,运行后检查this_task()->xcp.regs是否变了。如果变了,说明调度器切换了任务,返回新帧给汇编代码。ISR 中不直接切换任务——只修改调度器状态(
nxsched_add_readytorun()等)。实际切换延迟到arm_doirq()返回、汇编代码恢复寄存器时生效。enter_critical_section()=cpsid i(禁 IRQ)+ SMP 下的 spinlock。比sched_lock()更重量级,应尽量缩短使用时间。独立中断栈避免任务栈浪费——所有中断共享一块固定栈空间,推荐 >= 2048 字节。
14. 参考文件索引
| 文件路径 | 关键内容 | 引用行号 |
|---|---|---|
arch/arm/src/armv7-a/arm_vectors.S |
arm_vectorirq 汇编入口、中断栈分配 | 171-278, 796-828 |
arch/arm/src/armv7-a/arm_gicv2.c |
arm_decodeirq(), arm_gic0_initialize(), arm_gic_initialize() | 165-429 |
arch/arm/src/armv7-a/arm_doirq.c |
arm_doirq() 上下文切换检测 | 56-129 |
arch/arm/src/armv7-a/gic.h |
GIC 寄存器定义、SPI/PPI/SGI ID | 103-651 |
arch/arm/include/armv7-a/irq.h |
up_irq_save/restore, up_interrupt_context, 帧布局 | 367-494 |
sched/irq/irq.h |
struct irq_info_s, g_irqvector 声明 | 50-81 |
sched/irq/irq_initialize.c |
g_irqvector[] 定义和初始化 | 53-90 |
sched/irq/irq_attach.c |
irq_attach() ISR 注册 | 114-185 |
sched/irq/irq_dispatch.c |
irq_dispatch() ISR 分发 | 98-165 |
sched/irq/irq_csection.c |
enter/leave_critical_section() | 258-463 |
arch/arm/src/common/arm_internal.h |
g_intstackalloc/g_intstacktop 声明 | 201-204 |
include/nuttx/wqueue.h |
work_queue() API 定义 | 406 |
sched/wqueue/kwork_queue.c |
work_queue() 实现 | 70-157 |
drivers/ioexpander/icjx.c |
ISR + worker 模式实例 | 1013-1094 |
Documentation/implementation/interrupt_controls.rst |
NuttX 官方中断控制文档 | 全文 |
外部参考文档:
| 文档 | 说明 |
|---|---|
| ARM Architecture Reference Manual (ARMv7-A/R) | 异常模型、IRQ/FIQ 模式、CPSR 位定义 |
| ARM Generic Interrupt Controller Architecture Specification (GIC v2.0) | ICCIAR/ICCEOIR 寄存器、优先级模型、Binary Point |
| NuttX Documentation: https://nuttx.apache.org/docs/latest/ | 官方中断配置和驱动开发指南 |