4、中断子系统回顾:Linux中断处理流程、上半部与下半部机制、中断描述符与irq_chip结构体
在深入实时性补丁的中断线程化改造之前,必须彻底理解标准Linux内核的中断子系统。中断是内核响应硬件事件的核心机制,其设计直接决定了系统的实时响应能力。本章将系统回顾中断处理的全流程、上半部(Top Half)与下半部(Bottom Half)的协作模式,以及中断描述符与irq_chip结构体的底层抽象。
4.1 Linux中断处理流程全景
当硬件设备产生中断信号时,CPU会暂停当前执行流,跳转到预先设置的中断向量表(IDT)对应的入口函数。整个处理流程可以分为以下几个关键阶段:
- 硬件中断信号到达:外设通过中断请求线(IRQ)向中断控制器(如APIC、GIC)发送信号。
- 中断控制器仲裁:中断控制器根据优先级、屏蔽状态决定是否向CPU核心发送中断。
- CPU响应与现场保存:CPU保存当前上下文(寄存器、程序计数器),关闭本地中断,跳转到通用中断入口
entry_irq。 - 中断描述符查找:内核根据中断号查找对应的
irq_desc结构体。 - 执行中断控制器回调:调用
irq_chip中的irq_ack、irq_mask等硬件操作。 - 执行上半部处理函数:调用注册在
action链表中的handler,通常只做最紧急的硬件操作。 - 触发下半部:上半部通过
tasklet、workqueue或softirq调度下半部处理。 - 恢复现场与中断返回:恢复CPU上下文,开启中断,返回被中断的进程。
关键理解:中断处理的核心矛盾在于“快速响应硬件”与“避免阻塞高优先级任务”之间的平衡。上半部在关中断或屏蔽中断线的情况下执行,必须极短;下半部在开中断环境下执行,可以完成大部分数据处理。
4.2 上半部(Top Half)与下半部(Bottom Half)机制
Linux内核将中断处理拆分为两个阶段,以平衡实时性与吞吐量。上半部是“硬中断上下文”,下半部是“软中断上下文”或“进程上下文”。
4.2.1 上半部(Top Half)
- 执行环境:在中断上下文中运行,当前中断线被屏蔽(或CPU本地中断关闭)。
- 职责:确认中断、清除硬件中断标志、拷贝硬件寄存器数据到内存、调度下半部。
- 限制:不能睡眠、不能调用可能阻塞的函数、不能持有互斥锁。
- 注册方式:通过
request_irq()或request_threaded_irq()注册handler。
4.2.2 下半部(Bottom Half)
下半部机制经历了多次演进,目前主流实现包括:
| 机制 | 执行上下文 | 可睡眠 | 并发度 | 典型使用场景 |
|---|---|---|---|---|
| Softirq | 软中断上下文 | 否 | 同类型可多CPU并行 | 网络收发包、定时器 |
| Tasklet | 软中断上下文 | 否 | 同类型串行 | 块设备、简单驱动 |
| Workqueue | 进程上下文 | 是 | 可并行、可阻塞 | 复杂数据处理、I/O操作 |
| Threaded IRQ | 内核线程 | 是 | 按中断线隔离 | 实时性补丁强制使用 |
实时性视角:标准内核中,Softirq和Tasklet仍然在“软中断上下文”中执行,优先级高于普通进程,且不可被抢占。这会导致高优先级实时任务被延迟。实时性补丁(PREEMPT_RT)的核心改造之一就是将Softirq和Tasklet全部迁移到内核线程中执行,使其可被抢占。
4.3 中断描述符(irq_desc)结构体
每个中断号在Linux内核中对应一个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 cpumask *percpu_enabled;
#ifdef CONFIG_PREEMPT_RT
struct task_struct *thread; // 中断线程化后的内核线程
#endif
struct proc_dir_entry *dir;
const char *name;
} ____cacheline_internodealigned_in_smp;
关键字段说明:
handle_irq:中断流控处理函数,如handle_level_irq、handle_edge_irq,由中断控制器类型决定。action:链表头,挂载所有注册到该中断号的irqaction结构体(支持共享中断)。thread:在PREEMPT_RT内核中,每个中断描述符可以关联一个内核线程,用于执行中断线程化处理。kstat_irqs:每个CPU的中断发生次数统计。
注意:在标准内核中,
irq_desc的action链表中的handler直接在上半部执行。而在PREEMPT_RT内核中,如果中断被线程化,handler会在关联的内核线程上下文中执行,从而避免关中断时间过长。
4.4 irq_chip结构体:硬件操作抽象层
irq_chip是内核与中断控制器硬件之间的接口抽象。每个中断控制器(如IO-APIC、GIC、ARMv7M NVIC)都需要实现一组回调函数,封装在irq_chip结构体中。
// include/linux/irq.h (简化结构)
struct irq_chip {
const char *name; // 芯片名称
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data); // 应答中断
void (*irq_mask)(struct irq_data *data); // 屏蔽中断线
void (*irq_unmask)(struct irq_data *data); // 取消屏蔽
void (*irq_eoi)(struct irq_data *data); // 中断结束
int (*irq_set_affinity)(struct irq_data *data,
const struct cpumask *dest,
bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data,
unsigned int flow_type);
void (*irq_ack)(struct irq_data *data);
// ... 其他可选回调
};
核心回调函数作用:
| 回调函数 | 触发时机 | 典型操作 |
|---|---|---|
irq_ack |
中断处理开始时 | 向中断控制器发送应答信号,允许继续接收同类型中断 |
irq_mask |
需要屏蔽中断线时 | 设置中断控制器屏蔽寄存器,禁止该中断线传递信号 |
irq_unmask |
中断处理结束或重新使能时 | 清除屏蔽寄存器,允许中断线再次传递信号 |
irq_eoi |
中断处理完成时(EOI) | 通知中断控制器中断处理完毕,通常用于电平触发中断 |
irq_set_affinity |
设置中断亲和性时 | 将中断路由到指定CPU核心 |
irq_set_type |
配置中断触发方式时 | 设置电平触发、边沿触发、高电平有效等 |
4.5 中断处理流程中的实时性瓶颈
标准Linux内核的中断处理机制在实时性方面存在以下关键瓶颈:
- 上半部关中断时间不可控:复杂驱动的上半部可能执行较长时间,导致高优先级任务无法被调度。
- Softirq/Tasklet优先级过高:软中断上下文优先级高于所有进程,即使实时任务也无法抢占。
- 中断嵌套导致优先级反转:低优先级中断的上半部可能屏蔽高优先级中断。
- 中断亲和性配置不当:中断集中在某个CPU核心,导致该核心负载过高。
PREEMPT_RT的解决思路:将中断处理上半部中非紧急部分、以及所有下半部(Softirq/Tasklet)迁移到可被抢占的内核线程中执行。中断线程化后,实时任务可以通过优先级调度机制抢占中断处理线程,从而保证实时性。