2. Linux中断子系统架构:中断控制器(GIC)、中断描述符、中断处理流程(上半部/下半部)
在嵌入式实时系统中,中断是CPU响应外部或内部异步事件的硬件机制。Linux内核的中断子系统设计精巧,旨在平衡实时响应与通用处理的需求。本章节深入剖析中断子系统的三大核心组件:中断控制器(以GIC为例)、中断描述符管理以及经典的中断处理流程(上半部/下半部)。
2.1 中断控制器:GIC (Generic Interrupt Controller)
GIC是ARM架构中标准的中断控制器,负责管理来自多个中断源的中断请求,并将其路由到合适的CPU核心。理解GIC是掌握Linux中断子系统的基础。
核心概念: GIC的主要功能包括中断使能/禁能、中断优先级管理、中断分发与路由、以及中断状态跟踪(pending、active、active and pending)。
2.1.1 GIC架构分层
GIC逻辑上分为两个主要模块:分发器(Distributor)和CPU接口(CPU Interface)。
- 分发器(Distributor): 全局控制单元。负责配置每个中断的优先级、目标CPU、触发方式(电平/边沿),并控制中断的使能与禁能。它接收所有中断源,并根据配置将最高优先级的中断发送给CPU接口。
- CPU接口(CPU Interface): 每个CPU核心拥有一个独立的CPU接口。它负责与CPU核心交互,处理中断优先级屏蔽、中断抢占逻辑,并向CPU核心发送IRQ/FIQ信号。
2.1.2 中断类型与ID
GIC管理的中断分为三种类型,每种类型在系统中扮演不同角色:
| 中断类型 | ID范围 | 描述 | 典型用途 |
|---|---|---|---|
| SGI (Software Generated Interrupt) | 0 - 15 | 软件触发的中断,用于核间通信(IPI)。 | 调度、TLB刷新、CPU唤醒 |
| PPI (Private Peripheral Interrupt) | 16 - 31 | 私有外设中断,每个CPU核心独有。 | 本地定时器、性能监测单元 |
| SPI (Shared Peripheral Interrupt) | 32 - 1019 | 共享外设中断,可路由到任意CPU核心。 | UART、I2C、网络控制器 |
工程实践提示: 在中断线程化移植中,SPI类型的设备中断是主要关注对象。合理配置SPI的目标CPU亲和性(
/proc/irq/<irq_num>/smp_affinity)可以显著提升多核系统的负载均衡。
2.2 中断描述符:irq_desc
在Linux内核中,每个中断源(无论是硬件中断还是软件中断)都由一个中断描述符(struct irq_desc)表示。它是中断子系统的核心数据结构,串联起硬件中断与软件处理逻辑。
2.2.1 irq_desc结构体核心字段
irq_desc结构体定义在include/linux/irqdesc.h中,其关键字段如下:
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq; // 中断流处理函数
struct irqaction *action; // 中断动作链表
unsigned int status_use_accessors;
struct list_head pending; // 待处理中断链表
struct irq_affinity_notify *affinity_notify;
int parent_irq;
struct module *owner;
const char *name;
wait_queue_head_t wait_for_threads; // 等待线程完成
struct proc_dir_entry *dir;
struct cpumask *percpu_enabled;
// ... 其他字段
};
2.2.2 中断描述符管理机制
内核通过一个全局的irq_desc数组(或基数树)来管理所有中断描述符。中断号(IRQ number)是访问该数组的索引。
- 分配与初始化: 在系统启动或设备驱动注册时,通过
irq_alloc_descs()或irq_create_mapping()分配并初始化中断描述符。 - 中断流处理:
handle_irq字段指向一个高层的中断流处理函数(如handle_level_irq、handle_edge_irq),它负责处理中断控制器的硬件交互(如应答、屏蔽)。 - 中断动作链表:
action字段指向一个struct irqaction链表。每个irqaction代表一个注册的中断处理程序(上半部)。多个处理程序可以共享同一个中断号。
2.3 中断处理流程:上半部与下半部
为了在中断上下文中最小化关中断时间,Linux将中断处理拆分为两个阶段:上半部(Top Half)和下半部(Bottom Half)。这是实时系统设计中的核心思想。
2.3.1 上半部(Top Half)
上半部在中断上下文中执行,具有以下特点:
- 原子性: 执行期间,当前CPU核心的中断被屏蔽(取决于中断控制器的优先级设置)。
- 快速性: 必须快速完成,通常只做最必要的硬件操作,如读取状态寄存器、清除中断标志、将数据拷贝到内存缓冲区。
- 不可阻塞: 不能调用可能导致睡眠的函数(如
mutex_lock、kmalloc(GFP_KERNEL))。
2.3.2 下半部(Bottom Half)
下半部在稍后的时间点执行,此时中断已经重新使能。它负责处理上半部遗留的、不那么紧急的工作。Linux提供了多种下半部机制:
| 机制 | 执行上下文 | 特点 | 适用场景 |
|---|---|---|---|
| 软中断 (Softirq) | 软中断上下文 | 静态分配,可重入,同类型软中断可在多核并行执行。 | 网络收发、块设备层、高吞吐场景 |
| Tasklet | 软中断上下文 | 基于软中断实现,同一Tasklet串行执行,简单易用。 | 一般设备驱动下半部 |
| 工作队列 (Workqueue) | 进程上下文 | 允许睡眠,由内核线程执行,可调度。 | 需要阻塞操作的下半部(如I2C/SPC传输) |
| 中断线程化 (Threaded IRQ) | 进程上下文 | 为每个中断创建一个内核线程,完全在进程上下文处理。 | 实时性要求高、处理逻辑复杂的设备 |
2.3.3 完整中断处理流程
从硬件中断信号触发到软件处理完成,Linux内核经历以下关键步骤:
- 硬件中断到达: 外设向GIC发送中断信号。
- GIC分发与路由: GIC根据优先级和亲和性配置,将中断路由到目标CPU核心。
- CPU异常向量: CPU核心响应IRQ异常,跳转到内核预置的异常向量表(
vectors)。 - 进入中断上下文: 内核保存当前上下文,调用
handle_arch_irq或gic_handle_irq。 - 调用流处理函数: 根据中断号找到对应的
irq_desc,调用其handle_irq(如handle_level_irq)。 - 执行上半部: 遍历
action链表,执行所有注册的handler函数。 - 标记下半部: 如果
handler返回IRQ_WAKE_THREAD,则唤醒对应的中断线程(线程化中断);或者触发软中断/Tasklet。 - 退出中断上下文: 恢复上下文,使能中断。
- 执行下半部: 在适当的时机(如
do_softirq、内核线程调度),执行下半部处理函数。
实时性陷阱: 传统的上半部/下半部机制中,上半部执行时间不可控,且软中断/Tasklet的优先级高于普通进程,可能导致实时任务被延迟。中断线程化将下半部移至进程上下文,允许被实时调度器管理,是解决此问题的关键手段。
2.4 本章小结
本章深入剖析了Linux中断子系统的三大支柱:
- GIC中断控制器: 作为硬件中枢,负责中断的收集、优先级排序与分发。理解SGI/PPI/SPI的分类是进行中断亲和性调优的基础。
- 中断描述符(irq_desc): 内核中中断的“身份证”,串联起硬件中断号与软件处理逻辑。其
action链表和thread_fn字段是实现中断线程化的关键数据结构。 - 中断处理流程: 上半部快速处理硬件事件,下半部完成剩余工作。下半部机制的演进(从软中断到中断线程化)反映了实时性需求的不断提升。