一、中断的本质与硬件基础

中断(Interrupt)是计算机系统中处理器响应外部或内部事件的机制,它打破了程序的顺序执行流,使CPU能够及时处理硬件设备的请求。在Linux内核中,中断处理是系统响应性和实时性的核心保障。

1.1 中断的分类

从硬件层面,中断可分为两大类:

  • 外部中断(硬件中断):由外部硬件设备通过中断控制器(如8259A、APIC、GIC)向CPU引脚发送信号触发。例如键盘按键、网卡收到数据包、磁盘IO完成等。
  • 内部中断(异常/软中断):由CPU内部执行指令时产生。包括故障(Page Fault)、陷阱(System Call)、_ABORT(硬件错误)等。

1.2 x86 APIC 中断控制器架构

现代x86系统使用高级可编程中断控制器(APIC),包含Local APIC和I/O APIC两个核心组件:

  • Local APIC:集成在每个CPU核心中,接收来自I/O APIC的中断消息和处理器间中断(IPI),维护本地定时器和中断向量表。
  • I/O APIC:通常集成在南桥芯片中,负责接收来自外部设备的中断请求(IRQ线),并根据重定向表将中断路由到指定CPU的Local APIC。

1.3 ARM GIC 中断控制器架构

ARM架构使用通用中断控制器(GIC),当前主流版本为GICv3/GICv4:

  • SPI(Shared Peripheral Interrupt):共享外设中断,可被分组CPU访问,编号32到1019。
  • PPI(Private Peripheral Interrupt):私有外设中断,编号为16到31,每个CPU核心有独立的PPI信号。
  • LPI(Locality-specific Peripheral Interrupt):特定位置外设中断,基于消息的中断,GICv3引入,支持MSI/MSI-X。
  • SGI(Software Generated Interrupt):软件生成中断,用于处理器间通信,编号0到15。

二、中断处理的上下文与基本流程

当硬件中断发生时,处理器执行以下原子操作序列:

2.1 硬件自动操作序列

  1. 保存当前执行上下文:将EFLAGS、CS、EIP(或ARM的PSTATE/ELR)压入当前特权级的栈中。
  2. 确定中断向量号:从中断控制器获取中断索引号(IRQ number)。
  3. 切换到内核态:从TSS(x86)或VBAR(ARM)加载中断栈指针,提升特权级别。
  4. 跳转到中断服务例程(ISR):根据向量号在IDT(中断描述符表)中索引并跳转到对应处理函数。

2.2 内核ISR入口(以x86为例)

内核使用汇编实现入口点common_interrupt,快速保存所有通用寄存器到pt_regs结构体,然后调用C语言处理函数:

// arch/x86/kernel/entry_64.S 示意
ENTRY(common_interrupt)
    ASM_CLAC
    addq $-0x80, %rsp       // 对齐栈指针
    CFI_ADJUST_CFA_OFFSET 0x80
    movq %rdi, %rsp         // 保存pt_regs指针
    
    call do_IRQ              // 调用C语言中断处理
    jmp ret_from_intr        // 返回处理路径
END(common_interrupt)

2.3 IRQ Descriptor 与处理链

内核使用struct 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;       // 设备驱动注册的action链表
    unsigned int            status_use_accessors;
    unsigned int            depth;         // 嵌套深度
    unsigned int            wake_depth;    // 唤醒嵌套计数
    unsigned int            irq_count;     // 防中断风暴的计数器
    unsigned long           last_unhandled;
    unsigned int            irqs_unhandled;
    raw_spinlock_t          lock;
    struct cpumask          *percpu_enabled;
    const struct cpumask    *percpu_affinity;
    cpumask_var_t           affinity_hint;
    struct rcu_head         rcu;
    struct kobject          kobj;
    struct mutex            request_mutex;
    int                     parent_irq;
    struct module           *owner;
    const char              *name;
} ____cacheline_internodealigned_in_smp;

关键点:*action指针指向一个irqaction链表,每个注册该IRQ的驱动都会挂载一个节点——这就是共享IRQ(IRQ sharing)的实现基础。

三、Top Half 与 Bottom Half:中断分割的艺术

在处理硬件中断时,系统处于中断上下文(interrupt context),这意味着:

  • 中断处理执行时,本地CPU对应中断线被屏蔽(可能所有中断都被屏蔽)
  • 不能睡眠或阻塞(没有进程上下文背供调度)
  • 持有自旋锁期间执行时间过长会导致系统无响应

因此Linux将中断处理分为两个阶段:

  • Top Half(上半部):立即执行的硬件响应部分,关中断运行,仅做最关键的确认和数据搬运(如读取设备状态寄存器、清除中断标志)。
  • Bottom Half(下半部):延后执行的数据处理部分,开中断运行,可以执行较复杂的逻辑。

3.1 三大下半部机制对比

特性softirqtaskletworkqueue
执行上下文软中断上下文(原子态)softirq上下文(基于softirq)进程上下文(内核线程)
是否可睡眠否(不能睡眠)否(不能睡眠)是(可睡眠/阻塞)
并发性同类型可在不同CPU同时执行同类型串行(不同类型可并发)全局工作队列可并发
注册方式静态定义(open_softirq)动态初始化(tasklet_init)动态创建(INIT_WORK)
适用场景高频小任务(NET_RX, BLOCK)基于softirq的中断下半部需要睡眠的复杂操作
API复杂度低(受限)中高(功能丰富)

四、softirq:底层软中断引擎

4.1 softirq的类型与优先级

softirq在内核中静态定义,枚举值代表优先级(数字越小优先级越高):

// include/linux/interrupt.h
enum {
    HI_SOFTIRQ = 0,        // 高优先级tasklet
    TIMER_SOFTIRQ,         // 定时器
    NET_TX_SOFTIRQ,        // 网络发送
    NET_RX_SOFTIRQ,        // 网络接收(最高负载)
    BLOCK_SOFTIRQ,         // 块设备
    IRQ_POLL_SOFTIRQ,      // 中断轮询
    TASKLET_SOFTIRQ,       // 普通tasklet
    SCHED_SOFTIRQ,         // 调度器
    HRTIMER_SOFTIRQ,       // 高精度定时器
    RCU_SOFTIRQ,           // RCU回调
    NR_SOFTIRQS            // 总数限制为10
};

4.2 softirq的执行时机

softirq并非立即执行,而是在以下时刻被检查和触发:

  1. 硬件中断处理返回前(irq_exit 中调用 invoke_softirq)
  2. 显式调用 local_bh_enable 使能下半部时
  3. 软中断守护进程 ksoftirqd 被唤醒(当软中断过载时)

4.3 编写自定义softirq

注册softirq需要编译时静态声明,不推荐驱动开发者直接使用——大多数情况下应使用tasklet或workqueue。但了解其实现有助于理解内核设计:

// 1. 在枚举中插入自定义softirq(需要修改内核头文件)
// 2. 注册处理函数
open_softirq(MY_CUSTOM_SOFTIRQ, my_softirq_handler);

// 3. 在驱动中触发
raise_softirq(MY_CUSTOM_SOFTIRQ);

五、tasklet:softirq之上的串行化封装

tasklet基于softirq(HI_SOFTIRQ和TASKLET_SOFTIRQ)构建,提供更简单的API和串行化保证。

5.1 tasklet核心结构

// kernel/softirq.c
struct tasklet_struct {
    struct tasklet_struct *next;
    unsigned long state;     // TASKLET_STATE_SCHED / TASKLET_STATE_RUN
    atomic_t count;          // 引用计数(禁用计数器)
    void (*func)(unsigned long);  // 处理函数
    unsigned long data;      // 传入参数
};

// tasklet状态枚举
enum {
    TASKLET_STATE_SCHED,    // 已被调度,待执行
    TASKLET_STATE_RUN       // 正在某个CPU执行
};

5.2 tasklet API与使用示例

// 在驱动中声明并初始化tasklet
static void my_tasklet_handler(unsigned long data);
static DECLARE_TASKLET(my_tasklet, my_tasklet_handler, (unsigned long)dev);

// 或直接动态初始化
struct tasklet_struct *tl = kmalloc(sizeof(*tl), GFP_KERNEL);
tasklet_init(tl, my_handler, (unsigned long)dev);

// 在中断Top Half中调度tasklet
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_dev *dev = dev_id;
    
    // Top Half: 读取设备FIFO,写ACK清除中断标志
    u32 status = readl(dev->regs   REG_STATUS);
    writel(status                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部