2. Linux中断子系统架构
中断子系统是Linux内核与硬件交互的核心枢纽。本章深入剖析中断控制器(GIC)、中断描述符(irq_desc)以及完整的中断处理流程,为理解后续网络协议栈软中断协作机制奠定坚实基础。
2.1 中断控制器(GIC)架构与角色
在ARM多核处理器系统中,通用中断控制器(Generic Interrupt Controller, GIC)是管理所有中断源的核心硬件单元。GIC负责中断的优先级仲裁、分发到指定CPU核心以及中断屏蔽/使能控制。
GIC版本演进: GICv2(经典架构,支持最多8个PE)、GICv3(引入独立重分发器,支持更多PE和LPI)、GICv4(面向虚拟化,支持直接注入虚拟中断)。
关键概念: GIC将中断分为三类——SPI(共享外设中断,如网卡、USB)、PPI(私有外设中断,每个CPU独有,如本地定时器)、LPI(基于消息的中断,GICv3+引入,用于大量外设场景)。
2.2 中断描述符(irq_desc)与中断号映射
Linux内核使用中断描述符(irq_desc)数据结构来抽象和管理每个中断源。每个irq_desc对应一个唯一的软件中断号(IRQ number),内核通过该中断号索引到对应的描述符,进而执行中断处理。
| 字段 | 类型 | 说明 |
|---|---|---|
irq_data |
struct irq_data | 硬件中断号、芯片级操作函数指针 |
action |
struct irqaction * | 中断处理动作链表(共享中断时多个handler) |
status_use_accessors |
unsigned int | 中断状态标志(IRQ_INPROGRESS、IRQ_DISABLED等) |
depth |
unsigned int | 中断禁用深度计数(用于嵌套禁用/使能) |
lock |
raw_spinlock_t | 保护描述符的自旋锁 |
name |
const char * | 中断名称(如 "eth0"、"timer") |
中断号映射注意: 硬件中断号(HW IRQ)与软件中断号(Linux IRQ)之间通过
irq_domain进行映射。设备树或ACPI表描述硬件中断连接关系,内核在初始化时建立映射表。驱动开发中,platform_get_irq()返回的是软件中断号,而非硬件中断号。
2.3 中断处理流程:从硬件到软件
当外设触发中断时,Linux内核经历一套严谨的流程,从硬件信号到最终执行驱动注册的处理函数。整个流程分为上半部(硬中断)和下半部(软中断/tasklet/workqueue)两个阶段。
2.3.1 上半部(硬中断上下文)
- 硬件信号到达: 外设拉高中断请求线,GIC仲裁后发送IRQ信号到指定CPU核心。
- CPU异常向量跳转: CPU响应IRQ,跳转到异常向量表中的
irq_handler入口。 - 保存现场: 内核保存当前进程的寄存器状态,切换到中断栈。
- 调用
handle_arch_irq: 架构相关的中断入口函数,读取GIC的IAR寄存器获取硬件中断号。 - irq_domain转换: 将硬件中断号转换为Linux软件中断号。
- 执行
handle_irq_desc: 根据软件中断号找到irq_desc,调用其handle_irq函数(如handle_level_irq或handle_edge_irq)。 - 调用action链表: 遍历irq_desc->action链表,执行驱动注册的
handler函数(硬中断处理函数)。 - EOI(End of Interrupt): 写GIC的EOI寄存器,通知中断控制器处理完成。
硬中断上下文限制: 硬中断处理函数运行在中断上下文,不可睡眠、不可持有互斥锁、不可访问用户空间。必须快速完成(通常微秒级),耗时操作应委托给下半部。
2.3.2 下半部(软中断上下文)
硬中断处理函数通常只做最紧急的工作(如清除中断标志、拷贝数据),然后触发软中断(softirq)或tasklet来执行剩余处理。网络协议栈的NET_RX_SOFTIRQ和NET_TX_SOFTIRQ就是典型的软中断实例。
2.4 中断处理流程关键代码路径
以下代码展示了从异常入口到驱动handler的核心调用链(以ARM64架构为例,简化版):
// arch/arm64/kernel/irq.c
asmlinkage void __exception_irq_entry handle_arch_irq(struct pt_regs *regs)
{
unsigned long irqnr;
// 读取GIC IAR寄存器获取硬件中断号
irqnr = gic_read_iar();
// 转换为Linux软件中断号并调用handle_domain_irq
handle_domain_irq(gic_data.domain, irqnr, regs);
}
// kernel/irq/irqdesc.c
int handle_domain_irq(struct irq_domain *domain, unsigned int hwirq, struct pt_regs *regs)
{
struct irq_desc *desc;
unsigned int irq = irq_find_mapping(domain, hwirq); // 映射转换
desc = irq_to_desc(irq); // 获取irq_desc
generic_handle_irq_desc(desc); // 调用desc->handle_irq
}
// kernel/irq/chip.c
void handle_level_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc); // 屏蔽并应答中断
if (unlikely(desc->istate & IRQS_ONESHOT))
goto out;
// 遍历action链表执行handler
handle_irq_event(desc);
raw_spin_unlock(&desc->lock);
}
// kernel/irq/handle.c
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action;
irqreturn_t ret = IRQ_NONE;
// 遍历共享中断的action链表
for (action = desc->action; action; action = action->next)
ret |= action->handler(irq, action->dev_id);
return ret;
}
驱动开发关键点: 驱动通过
request_irq()或request_threaded_irq()注册handler。对于网络设备,通常注册一个轻量级硬中断handler,在其中调用napi_schedule()触发NAPI轮询(基于软中断),实现高效收包。
2.5 中断处理流程总结表
| 阶段 | 上下文 | 主要操作 | 耗时要求 | 可调用函数 |
|---|---|---|---|---|
| 上半部(硬中断) | 中断上下文 | 清除中断标志、拷贝数据、触发下半部 | 微秒级 | writel()、napi_schedule()、tasklet_schedule() |
| 下半部(软中断) | 软中断上下文 | 网络协议处理、数据包分发 | 毫秒级 | skb_copy()、netif_receive_skb() |
| 下半部(tasklet) | 软中断上下文 | 通用延迟处理 | 毫秒级 | kfree()、wake_up() |
| 下半部(workqueue) | 进程上下文 | 可睡眠的复杂操作 | 无严格限制 | mutex_lock()、kmalloc() |
性能陷阱: 硬中断处理时间过长会导致中断嵌套丢失、CPU响应延迟增加。网络高吞吐场景下,应优先使用NAPI(基于软中断)而非纯硬中断模式,以减少中断次数、提升吞吐量。