i386 架构中断管理函数详解

i386 架构中断管理函数详解

概述

本文档详细解释 i386 架构下中断管理相关函数的实现,包括:

  • request_irq - 注册中断处理程序
  • free_irq - 释放中断处理程序
  • disable_irq - 禁用中断(等待完成)
  • disable_irq_nosync - 禁用中断(不等待)
  • enable_irq - 启用中断

这些函数在 Linux 2.6.10 内核中的实现分为两个层次:

  1. 通用层kernel/irq/manage.c - 提供通用的中断管理逻辑
  2. 架构层arch/i386/kernel/i8259.c - 提供 i386 架构特定的硬件操作

1. request_irq - 注册中断处理程序

函数原型

c 复制代码
int request_irq(unsigned int irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned long irqflags, 
                const char *devname, 
                void *dev_id)

实现代码

c 复制代码
// kernel/irq/manage.c:310-346
int request_irq(unsigned int irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned long irqflags, const char * devname, void *dev_id)
{
    struct irqaction * action;
    int retval;

    // 参数检查:共享中断必须提供 dev_id
    if ((irqflags & SA_SHIRQ) && !dev_id)
        return -EINVAL;
    if (irq >= NR_IRQS)
        return -EINVAL;
    if (!handler)
        return -EINVAL;

    // 分配 irqaction 结构
    action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
    if (!action)
        return -ENOMEM;

    // 初始化 irqaction 结构
    action->handler = handler;
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;

    // 调用 setup_irq 设置中断
    retval = setup_irq(irq, action);
    if (retval)
        kfree(action);

    return retval;
}

代码逻辑解释

  1. 参数验证

    • 如果设置了 SA_SHIRQ(共享中断)标志,必须提供 dev_id
    • 检查 irq 是否在有效范围内(< NR_IRQS
    • 检查 handler 是否为空
  2. 分配内存

    • 使用 kmalloc 分配 struct irqaction 结构
    • 使用 GFP_ATOMIC 标志,因为可能在中断上下文中调用
  3. 初始化结构

    • 设置中断处理函数 handler
    • 设置标志位 flags(如 SA_SHIRQSA_INTERRUPT 等)
    • 清空 CPU 掩码
    • 设置设备名称和 dev_id
  4. 调用 setup_irq

    • irqaction 添加到中断描述符的 action 链表
    • 如果是第一个处理程序,启用中断线

setup_irq 函数

c 复制代码
// kernel/irq/manage.c:153-218
int setup_irq(unsigned int irq, struct irqaction * new)
{
    struct irq_desc *desc = irq_desc + irq;
    struct irqaction *old, **p;
    unsigned long flags;
    int shared = 0;

    // 检查中断线是否已配置有效的中断控制器
    if (desc->handler == &no_irq_type)
        return -ENOSYS;

    // 如果设置了 SA_SAMPLE_RANDOM,初始化随机数生成器
    if (new->flags & SA_SAMPLE_RANDOM) {
        rand_initialize_irq(irq);
    }

    // 原子操作:加锁
    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;
    
    // 检查是否已有处理程序
    if ((old = *p) != NULL) {
        // 必须都支持共享才能共享
        if (!(old->flags & new->flags & SA_SHIRQ)) {
            spin_unlock_irqrestore(&desc->lock, flags);
            return -EBUSY;
        }

        // 将新处理程序添加到链表末尾
        do {
            p = &old->next;
            old = *p;
        } while (old);
        shared = 1;
    }

    // 添加新处理程序
    *p = new;

    // 如果是第一个处理程序,启用中断
    if (!shared) {
        desc->depth = 0;
        desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT |
                          IRQ_WAITING | IRQ_INPROGRESS);
        if (desc->handler->startup)
            desc->handler->startup(irq);
        else
            desc->handler->enable(irq);
    }
    spin_unlock_irqrestore(&desc->lock, flags);

    new->irq = irq;
    register_irq_proc(irq);
    new->dir = NULL;
    register_handler_proc(irq, new);

    return 0;
}

关键点

  • 使用自旋锁保护临界区
  • 支持中断共享(多个设备共享同一中断线)
  • 第一个处理程序注册时启用中断线
  • 调用硬件相关的 enablestartup 函数

2. free_irq - 释放中断处理程序

函数原型

c 复制代码
void free_irq(unsigned int irq, void *dev_id)

实现代码

c 复制代码
// kernel/irq/manage.c:234-277
void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc;
    struct irqaction **p;
    unsigned long flags;

    if (irq >= NR_IRQS)
        return;

    desc = irq_desc + irq;
    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;
    
    // 遍历 action 链表,查找匹配的 dev_id
    for (;;) {
        struct irqaction * action = *p;

        if (action) {
            struct irqaction **pp = p;

            p = &action->next;
            // 如果 dev_id 不匹配,继续查找
            if (action->dev_id != dev_id)
                continue;

            // 找到匹配的处理程序,从链表中移除
            *pp = action->next;
            
            // 如果没有其他处理程序,禁用中断
            if (!desc->action) {
                desc->status |= IRQ_DISABLED;
                if (desc->handler->shutdown)
                    desc->handler->shutdown(irq);
                else
                    desc->handler->disable(irq);
            }
            spin_unlock_irqrestore(&desc->lock, flags);
            unregister_handler_proc(irq, action);

            // 等待其他 CPU 上的中断处理完成
            synchronize_irq(irq);
            kfree(action);
            return;
        }
        // 没有找到匹配的处理程序
        printk(KERN_ERR "Trying to free free IRQ%d\n", irq);
        spin_unlock_irqrestore(&desc->lock, flags);
        return;
    }
}

代码逻辑解释

  1. 参数检查 :检查 irq 是否有效

  2. 查找处理程序

    • 遍历 desc->action 链表
    • 通过 dev_id 匹配要释放的处理程序
    • 在共享中断中,dev_id 用于区分不同的设备
  3. 移除处理程序

    • 从链表中移除匹配的 irqaction
    • 如果这是最后一个处理程序,禁用中断线
  4. 同步等待

    • 调用 synchronize_irq(irq) 等待其他 CPU 上的中断处理完成
    • 确保处理程序不再被调用后再释放内存
  5. 清理资源

    • /proc 文件系统注销
    • 释放 irqaction 结构的内存

3. disable_irq_nosync - 禁用中断(不等待)

函数原型

c 复制代码
void disable_irq_nosync(unsigned int irq)

实现代码

c 复制代码
// kernel/irq/manage.c:52-63
void disable_irq_nosync(unsigned int irq)
{
    irq_desc_t *desc = irq_desc + irq;
    unsigned long flags;

    spin_lock_irqsave(&desc->lock, flags);
    // 增加嵌套深度
    if (!desc->depth++) {
        // 如果是第一次禁用,设置 IRQ_DISABLED 标志
        desc->status |= IRQ_DISABLED;
        // 调用硬件相关的 disable 函数
        desc->handler->disable(irq);
    }
    spin_unlock_irqrestore(&desc->lock, flags);
}

代码逻辑解释

  1. 嵌套计数

    • desc->depth 记录禁用的嵌套深度
    • 每次调用 disable_irq_nosync 都会增加 depth
    • 只有第一次禁用(depth 从 0 变为 1)时才真正禁用硬件中断
  2. 硬件操作

    • 设置 IRQ_DISABLED 状态标志
    • 调用硬件相关的 disable 函数(如 disable_8259A_irq
  3. 不等待

    • 函数立即返回,不等待正在执行的中断处理程序完成
    • 适用于中断上下文或需要快速禁用中断的场景

i386 架构的硬件实现

c 复制代码
// arch/i386/kernel/i8259.c:90-102
void disable_8259A_irq(unsigned int irq)
{
    unsigned int mask = 1 << irq;
    unsigned long flags;

    spin_lock_irqsave(&i8259A_lock, flags);
    // 在缓存掩码中设置对应位
    cached_irq_mask |= mask;
    // 根据中断号选择主 PIC 或从 PIC
    if (irq & 8)
        outb(cached_slave_mask, PIC_SLAVE_IMR);
    else
        outb(cached_master_mask, PIC_MASTER_IMR);
    spin_unlock_irqrestore(&i8259A_lock, flags);
}

硬件操作

  • 8259A PIC(可编程中断控制器)通过 IMR(中断屏蔽寄存器)控制中断
  • 主 PIC 控制 IRQ 0-7,从 PIC 控制 IRQ 8-15
  • 通过 outb 写入 IMR 寄存器来屏蔽中断

4. disable_irq - 禁用中断(等待完成)

函数原型

c 复制代码
void disable_irq(unsigned int irq)

实现代码

c 复制代码
// kernel/irq/manage.c:79-86
void disable_irq(unsigned int irq)
{
    irq_desc_t *desc = irq_desc + irq;

    // 先禁用中断(不等待)
    disable_irq_nosync(irq);
    // 如果有处理程序正在执行,等待其完成
    if (desc->action)
        synchronize_irq(irq);
}

代码逻辑解释

  1. 调用 disable_irq_nosync

    • 先禁用中断硬件
  2. 等待完成

    • 如果有中断处理程序正在执行(desc->action 不为空)
    • 调用 synchronize_irq(irq) 等待其完成

synchronize_irq 函数

c 复制代码
// kernel/irq/manage.c:29-35
void synchronize_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_desc + irq;

    // 等待中断处理完成
    while (desc->status & IRQ_INPROGRESS)
        cpu_relax();
}

关键点

  • IRQ_INPROGRESS 标志表示中断正在处理中
  • 使用 cpu_relax() 优化忙等待循环(不会让出 CPU)
  • 确保在禁用中断后,不会有处理程序继续执行

关于 cpu_relax()

在 i386 架构中,cpu_relax() 的实现是:

c 复制代码
// include/asm-i386/processor.h:587-592
static inline void rep_nop(void)
{
    __asm__ __volatile__("rep;nop": : :"memory");
}

#define cpu_relax()	rep_nop()

rep;nop 指令的作用

  • 这是 x86 的 PAUSE 指令(在较新的 CPU 上)
  • 不会让出 CPU,仍然在同一个 CPU 上执行
  • 提示 CPU 这是一个忙等待循环,可以降低 CPU 功耗
  • 本质上仍然是忙等待,只是优化了循环

注意cpu_relax() 不会让出 CPU,它只是优化了忙等待循环。如果需要真正让出 CPU,应该使用 schedule()cond_resched()


5. enable_irq - 启用中断

函数原型

c 复制代码
void enable_irq(unsigned int irq)

实现代码

c 复制代码
// kernel/irq/manage.c:100-125
void enable_irq(unsigned int irq)
{
    irq_desc_t *desc = irq_desc + irq;
    unsigned long flags;

    spin_lock_irqsave(&desc->lock, flags);
    switch (desc->depth) {
    case 0:
        // 深度为 0 表示已经启用,不应该调用 enable_irq
        WARN_ON(1);
        break;
    case 1: {
        // 最后一次启用,真正启用中断
        unsigned int status = desc->status & ~IRQ_DISABLED;

        desc->status = status;
        // 如果有挂起的中断,重新发送
        if ((status & (IRQ_PENDING | IRQ_REPLAY)) == IRQ_PENDING) {
            desc->status = status | IRQ_REPLAY;
            hw_resend_irq(desc->handler, irq);
        }
        // 调用硬件相关的 enable 函数
        desc->handler->enable(irq);
        /* fall-through */
    }
    default:
        // 减少嵌套深度
        desc->depth--;
    }
    spin_unlock_irqrestore(&desc->lock, flags);
}

代码逻辑解释

  1. 嵌套计数

    • desc->depth 记录禁用的嵌套深度
    • 每次调用 enable_irq 都会减少 depth
    • 只有最后一次启用(depth 从 1 变为 0)时才真正启用硬件中断
  2. 处理挂起中断

    • 如果中断被禁用期间有中断发生,会设置 IRQ_PENDING 标志
    • 启用中断时,如果有挂起的中断,会重新发送中断
  3. 硬件操作

    • 清除 IRQ_DISABLED 状态标志
    • 调用硬件相关的 enable 函数(如 enable_8259A_irq

i386 架构的硬件实现

c 复制代码
// arch/i386/kernel/i8259.c:104-116
void enable_8259A_irq(unsigned int irq)
{
    unsigned int mask = ~(1 << irq);
    unsigned long flags;

    spin_lock_irqsave(&i8259A_lock, flags);
    // 在缓存掩码中清除对应位
    cached_irq_mask &= mask;
    // 根据中断号选择主 PIC 或从 PIC
    if (irq & 8)
        outb(cached_slave_mask, PIC_SLAVE_IMR);
    else
        outb(cached_master_mask, PIC_MASTER_IMR);
    spin_unlock_irqrestore(&i8259A_lock, flags);
}

硬件操作

  • 清除 IMR 寄存器中对应中断的屏蔽位
  • 允许该中断线产生中断

6. 数据结构

irq_desc - 中断描述符

c 复制代码
// include/linux/irq.h:61-70
typedef struct irq_desc {
    hw_irq_controller *handler;      // 硬件中断控制器
    void *handler_data;               // 控制器私有数据
    struct irqaction *action;         // 中断处理程序链表
    unsigned int status;              // 中断状态标志
    unsigned int depth;               // 嵌套禁用深度
    unsigned int irq_count;           // 中断计数
    unsigned int irqs_unhandled;     // 未处理的中断计数
    spinlock_t lock;                  // 保护锁
} ____cacheline_aligned irq_desc_t;

irqaction - 中断处理程序

c 复制代码
// include/linux/interrupt.h:36-45
struct irqaction {
    irqreturn_t (*handler)(int, void *, struct pt_regs *);  // 处理函数
    unsigned long flags;              // 标志(SA_SHIRQ 等)
    cpumask_t mask;                   // CPU 掩码
    const char *name;                    // 设备名称
    void *dev_id;                      // 设备标识符
    struct irqaction *next;             // 下一个处理程序(共享中断)
    int irq;                           // 中断号
    struct proc_dir_entry *dir;         // /proc 目录项
};

hw_interrupt_type - 硬件中断控制器

c 复制代码
// include/linux/irq.h:41-50
struct hw_interrupt_type {
    const char *typename;              // 控制器类型名称
    unsigned int (*startup)(unsigned int irq);   // 启动中断
    void (*shutdown)(unsigned int irq);           // 关闭中断
    void (*enable)(unsigned int irq);            // 启用中断
    void (*disable)(unsigned int irq);            // 禁用中断
    void (*ack)(unsigned int irq);               // 确认中断
    void (*end)(unsigned int irq);                // 结束中断处理
    void (*set_affinity)(unsigned int irq, cpumask_t dest);  // 设置 CPU 亲和性
};

7. 关键概念

嵌套禁用/启用

  • disable_irqenable_irq 支持嵌套调用

  • 使用 desc->depth 计数,只有深度为 0 时才真正启用/禁用硬件

  • 例如:

    c 复制代码
    disable_irq(5);  // depth = 1,禁用硬件
    disable_irq(5);  // depth = 2,不操作硬件
    enable_irq(5);   // depth = 1,不操作硬件
    enable_irq(5);   // depth = 0,启用硬件

中断共享

  • 多个设备可以共享同一中断线(SA_SHIRQ 标志)
  • 每个设备必须提供唯一的 dev_id
  • 中断发生时,所有注册的处理程序都会被调用
  • 处理程序需要检查是否是自己的设备产生的中断

中断状态标志

  • IRQ_DISABLED:中断被禁用
  • IRQ_INPROGRESS:中断正在处理中
  • IRQ_PENDING:中断挂起(禁用期间发生)
  • IRQ_REPLAY:中断需要重新发送

8259A PIC 操作

  • 主 PIC:控制 IRQ 0-7,端口 0x20(命令)、0x21(数据)
  • 从 PIC:控制 IRQ 8-15,端口 0xA0(命令)、0xA1(数据)
  • IMR(中断屏蔽寄存器):控制哪些中断被屏蔽
  • EOI(中断结束):处理完中断后需要发送 EOI 信号

8. 使用示例

注册中断处理程序

c 复制代码
static irqreturn_t my_interrupt_handler(int irq, void *dev_id, struct pt_regs *regs)
{
    // 处理中断
    return IRQ_HANDLED;
}

// 注册中断
if (request_irq(IRQ_NUM, my_interrupt_handler, 
                SA_INTERRUPT | SA_SHIRQ, "my_device", dev)) {
    // 注册失败
    return -EBUSY;
}

释放中断处理程序

c 复制代码
// 释放中断
free_irq(IRQ_NUM, dev);

禁用/启用中断

c 复制代码
// 禁用中断(等待完成)
disable_irq(IRQ_NUM);

// 执行关键代码...

// 启用中断
enable_irq(IRQ_NUM);

// 或者快速禁用(不等待)
disable_irq_nosync(IRQ_NUM);
// 执行关键代码...
enable_irq(IRQ_NUM);

9. no_irq_type 详解

什么是 no_irq_type?

no_irq_type 是一个特殊的"空"中断控制器类型,表示该中断线没有配置有效的中断控制器

初始化状态

c 复制代码
// kernel/irq/handle.c:31-37
irq_desc_t irq_desc[NR_IRQS] __cacheline_aligned = {
    [0 ... NR_IRQS-1] = {
        .status = IRQ_DISABLED,
        .handler = &no_irq_type,  // 初始化为"无控制器"
        .lock = SPIN_LOCK_UNLOCKED
    }
};

所有中断描述符在初始化时都设置为 no_irq_type

i386 架构的初始化

c 复制代码
// arch/i386/kernel/i8259.c:362-388
void __init init_ISA_irqs(void)
{
    int i;
    
    init_8259A(0);  // 初始化 8259A PIC
    
    for (i = 0; i < NR_IRQS; i++) {
        irq_desc[i].status = IRQ_DISABLED;
        irq_desc[i].action = NULL;
        irq_desc[i].depth = 1;
        
        if (i < 16) {
            // IRQ 0-15:配置为使用 8259A PIC
            irq_desc[i].handler = &i8259A_irq_type;
        } else {
            // IRQ 16+:保持为 no_irq_type
            // 这些通常是 PCI 中断,需要按需配置
            irq_desc[i].handler = &no_irq_type;
        }
    }
}

关键点

  • IRQ 0-15 :在初始化时配置为使用 i8259A_irq_type(8259A PIC)
  • IRQ 16+ :保持为 no_irq_type,直到被实际配置(通常是 PCI 中断)

setup_irq 中的检查(第 103-105 行)

c 复制代码
// kernel/irq/manage.c:103-105
if (desc->handler == &no_irq_type)
    return -ENOSYS;

这个检查的含义

  1. 目的:确保该中断线已经配置了有效的中断控制器

  2. 如果为 no_irq_type

    • 表示该中断线还没有被初始化
    • 无法注册中断处理程序
    • 返回 -ENOSYS(函数未实现/不支持)
  3. 常见场景

    • 尝试在未配置的中断线上注册处理程序
    • 某些高编号的 PCI 中断可能还没有被配置
    • 需要先配置中断控制器,然后才能注册处理程序

no_irq_type 的实现

c 复制代码
// kernel/irq/handle.c:57-66
struct hw_interrupt_type no_irq_type = {
    .typename = "none",
    .startup = startup_none,      // 空函数
    .shutdown = shutdown_none,     // 空函数
    .enable = enable_none,         // 空函数
    .disable = disable_none,       // 空函数
    .ack = ack_none,               // 调用 ack_bad_irq
    .end = end_none,               // 空函数
    .set_affinity = NULL
};

特点

  • 所有操作函数都是空函数或错误处理函数
  • ack_none 会调用 ack_bad_irq(irq) 报告错误
  • 表示该中断线不可用

总结no_irq_type 检查确保只有在已配置的中断线上才能注册处理程序,防止在无效的中断线上进行操作。


10. 总结

  1. request_irq:分配并注册中断处理程序,支持中断共享
  2. free_irq:释放中断处理程序,等待处理完成后再释放
  3. disable_irq_nosync:快速禁用中断,不等待处理完成
  4. disable_irq:禁用中断并等待处理完成
  5. enable_irq:启用中断,支持嵌套调用

这些函数提供了完整的中断管理机制,既支持单设备独占中断,也支持多设备共享中断,同时通过嵌套计数机制支持安全的嵌套禁用/启用操作。

相关推荐
kkkkkkkkl2410 小时前
Prometheus指标入门详解
linux·服务器
釉色清风10 小时前
在openEuler玩转Python
linux·开发语言·python
infiniteWei12 小时前
【VIM 入门到精通】第1节:揭开Vim的神秘面纱:入门与基础操作
linux·编辑器·vim
卌卄12 小时前
Linux下安装Docker
linux·运维·docker
小猿成长13 小时前
Ubuntu搭建物联网平台(ThingsBoard)教程
linux·运维·ubuntu
Archie_IT13 小时前
openEuler 软件生态深度勘探:从六万软件包到多语言融合
linux·容器·性能测试·openeuler·多语言开发
Linux_Nathan14 小时前
【服务部署】ELFK架构篇之Elasticsearch
elk·elasticsearch·架构
tokepson14 小时前
香橙派AI Pro个人云平台 - 从零搭建全记录
linux·服务器·技术·记录
fengyehongWorld15 小时前
Linux wget命令
linux