Linux中断处理

1. Linux中断概述

中断是硬件与CPU通信的一种机制,当硬件需要CPU处理时,通过中断信号通知CPU。

1.1 中断类型

  • 硬件中断:由硬件设备产生(键盘、鼠标、网卡等)
  • 软件中断:由软件指令产生
  • 异常:CPU执行指令时产生的错误

1.2 中断处理特点

  • 异步执行:中断可以在任何时候发生
  • 中断上下文:执行环境限制较多
  • 上半部/下半部:将中断处理分为紧急和非紧急部分

2. 中断处理基础

2.1 注册中断处理程序

#include <linux/interrupt.h>

// 中断处理函数原型
irqreturn_t interrupt_handler(int irq, void *dev_id);

// 注册中断处理函数
int request_irq(unsigned int irq, irq_handler_t handler, 
                unsigned long flags, const char *name, void *dev);

// 释放中断
void free_irq(unsigned int irq, void *dev_id);

2.2 简单中断示例

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>

#define GPIO_IRQ 17  // GPIO引脚号
static int irq_number;

// 中断处理函数
static irqreturn_t gpio_interrupt_handler(int irq, void *dev_id)
{
    printk(KERN_INFO "Interrupt occurred on GPIO %d\n", irq);
    
    // 读取GPIO状态或其他处理
    // 注意:在中断上下文中不能睡眠!
    
    return IRQ_HANDLED;  // 中断已处理
}

static int __init my_interrupt_init(void)
{
    int ret;
    
    printk(KERN_INFO "Initializing interrupt module\n");
    
    // 注册中断处理程序
    ret = request_irq(GPIO_IRQ, gpio_interrupt_handler, 
                     IRQF_TRIGGER_RISING, "my_gpio_irq", NULL);
    
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", GPIO_IRQ);
        return ret;
    }
    
    irq_number = GPIO_IRQ;
    printk(KERN_INFO "Interrupt handler registered for IRQ %d\n", GPIO_IRQ);
    
    return 0;
}

static void __exit my_interrupt_exit(void)
{
    free_irq(irq_number, NULL);
    printk(KERN_INFO "Interrupt handler unregistered\n");
}

module_init(my_interrupt_init);
module_exit(my_interrupt_exit);
MODULE_LICENSE("GPL");

3. 中断标志和类型

3.1 中断标志

// 中断触发类型
IRQF_TRIGGER_RISING     // 上升沿触发
IRQF_TRIGGER_FALLING    // 下降沿触发  
IRQF_TRIGGER_HIGH       // 高电平触发
IRQF_TRIGGER_LOW        // 低电平触发

// 中断处理标志
IRQF_SHARED             // 共享中断
IRQF_ONESHOT            // 一次性中断,处理完成后需要重新使能
IRQF_TIMER              // 定时器中断
IRQF_NO_THREAD          // 禁止线程化处理
IRQF_EARLY_RESUME       // 早期恢复

3.2 共享中断示例

#include <linux/module.h>
#include <linux/interrupt.h>

#define SHARED_IRQ 9
static int irq = SHARED_IRQ;
static int dev_id = 1;

// 共享中断处理函数
static irqreturn_t shared_interrupt_handler(int irq, void *dev_id)
{
    int *my_id = (int *)dev_id;
    
    // 检查是否是我们的设备产生的中断
    if (!is_my_device_interrupt()) {
        return IRQ_NONE;  // 不是我们的中断
    }
    
    printk(KERN_INFO "Shared interrupt handled by device %d\n", *my_id);
    
    // 处理中断
    handle_device_interrupt();
    
    return IRQ_HANDLED;
}

static int __init shared_irq_init(void)
{
    int ret;
    
    // 注册共享中断
    ret = request_irq(irq, shared_interrupt_handler, 
                     IRQF_SHARED | IRQF_TRIGGER_RISING,
                     "my_shared_irq", &dev_id);
    
    if (ret) {
        printk(KERN_ERR "Failed to request shared IRQ\n");
        return ret;
    }
    
    printk(KERN_INFO "Shared interrupt handler registered\n");
    return 0;
}

static void __exit shared_irq_exit(void)
{
    free_irq(irq, &dev_id);
    printk(KERN_INFO "Shared interrupt handler unregistered\n");
}

4. 中断上下文限制

4.1 中断上下文的特点

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/sched.h>

static int test_irq;

static irqreturn_t test_interrupt_handler(int irq, void *dev_id)
{
    printk(KERN_INFO "Interrupt context information:\n");
    
    // 检查当前上下文
    if (in_interrupt()) {
        printk(KERN_INFO "  - We are in interrupt context\n");
    }
    
    if (in_irq()) {
        printk(KERN_INFO "  - We are in hard IRQ context\n");
    }
    
    if (in_softirq()) {
        printk(KERN_INFO "  - We are in softirq context\n");
    }
    
    // 中断上下文中不能做的事情:
    // 1. 不能访问用户空间
    // 2. 不能执行可能睡眠的操作
    // 3. 不能调用可能阻塞的函数
    
    // 错误的做法(会导致内核错误):
    // copy_to_user(...);      // 可能睡眠
    // kmalloc(GFP_KERNEL);    // 可能睡眠
    // mutex_lock(...);        // 可能睡眠
    
    // 正确的做法:
    // copy_to_user 的替代:先缓存数据,在进程上下文中处理
    // kmalloc(GFP_ATOMIC);    // 使用原子分配
    // spin_lock(...);         // 使用自旋锁
    
    return IRQ_HANDLED;
}

5. 底半部机制

由于中断处理程序的限制,Linux将中断处理分为两部分:

  • 顶半部:紧急处理,在中断上下文中执行
  • 底半部:非紧急处理,可以睡眠和阻塞

5.1 软中断 (Softirq)

// 软中断是内核底层的底半部机制
// 通常由内核核心代码使用,驱动程序较少直接使用

5.2 Tasklet

#include <linux/interrupt.h>

// Tasklet声明和初始化
DECLARE_TASKLET(my_tasklet, tasklet_function, (unsigned long)data);
DECLARE_TASKLET_DISABLED(my_tasklet, tasklet_function, (unsigned long)data);

// Tasklet处理函数
void tasklet_function(unsigned long data)
{
    // 这里可以执行较耗时的操作
    // 但仍然在软中断上下文中,不能睡眠
    printk(KERN_INFO "Tasklet executing with data: %lu\n", data);
}

// 在中断处理函数中调度tasklet
static irqreturn_t irq_handler_with_tasklet(int irq, void *dev_id)
{
    // 紧急处理
    handle_urgent_work();
    
    // 调度tasklet处理非紧急工作
    tasklet_schedule(&my_tasklet);
    
    return IRQ_HANDLED;
}

// 初始化和清理
static int __init tasklet_example_init(void)
{
    // 初始化tasklet
    tasklet_init(&my_tasklet, tasklet_function, 0x1234);
    return 0;
}

static void __exit tasklet_example_exit(void)
{
    // 禁用并等待tasklet完成
    tasklet_kill(&my_tasklet);
}

5.3 工作队列 (Workqueue)

#include <linux/workqueue.h>

// 工作队列声明
static struct workqueue_struct *my_wq;
static struct work_struct my_work;

// 工作处理函数
void work_handler(struct work_struct *work)
{
    // 在进程上下文中执行,可以睡眠
    printk(KERN_INFO "Work queue handler executing\n");
    
    // 可以执行耗时的操作
    // 可以调用可能睡眠的函数
    msleep(1000);  // 在工作队列中可以睡眠
    
    // 可以分配内存(可能睡眠)
    char *buf = kmalloc(1024, GFP_KERNEL);
    if (buf) {
        // 处理数据
        kfree(buf);
    }
}

// 在中断处理函数中调度工作
static irqreturn_t irq_handler_with_workqueue(int irq, void *dev_id)
{
    // 紧急处理
    handle_urgent_work();
    
    // 调度工作队列处理非紧急工作
    queue_work(my_wq, &my_work);
    
    return IRQ_HANDLED;
}

// 延迟工作队列
static struct delayed_work my_delayed_work;

void delayed_work_handler(struct work_struct *work)
{
    printk(KERN_INFO "Delayed work executed after 2 seconds\n");
}

static int __init workqueue_example_init(void)
{
    // 创建工作队列
    my_wq = create_singlethread_workqueue("my_workqueue");
    if (!my_wq) {
        return -ENOMEM;
    }
    
    // 初始化工作
    INIT_WORK(&my_work, work_handler);
    INIT_DELAYED_WORK(&my_delayed_work, delayed_work_handler);
    
    // 调度延迟工作(2秒后执行)
    queue_delayed_work(my_wq, &my_delayed_work, msecs_to_jiffies(2000));
    
    return 0;
}

static void __exit workqueue_example_exit(void)
{
    // 取消延迟工作
    cancel_delayed_work(&my_delayed_work);
    
    // 刷新并销毁工作队列
    flush_workqueue(my_wq);
    destroy_workqueue(my_wq);
}

6. 完整的中断处理示例

6.1 网络设备中断处理

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

struct my_net_device {
    struct net_device *netdev;
    int irq;
    struct napi_struct napi;
    spinlock_t lock;
    struct work_struct tx_work;
};

// NAPI轮询函数
int my_napi_poll(struct napi_struct *napi, int budget)
{
    struct my_net_device *dev = container_of(napi, struct my_net_device, napi);
    int work_done = 0;
    
    // 处理接收的数据包
    while (work_done < budget && has_rx_packets(dev)) {
        if (process_rx_packet(dev) < 0) {
            break;
        }
        work_done++;
    }
    
    // 如果所有数据包都处理完了,禁用NAPI
    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        // 重新使能中断
        enable_irq(dev->irq);
    }
    
    return work_done;
}

// 中断处理函数
static irqreturn_t netdev_interrupt_handler(int irq, void *dev_id)
{
    struct my_net_device *dev = dev_id;
    
    // 禁用中断,使用NAPI处理
    disable_irq_nosync(irq);
    
    // 调度NAPI
    if (napi_schedule_prep(&dev->napi)) {
        __napi_schedule(&dev->napi);
    }
    
    return IRQ_HANDLED;
}

// 发送工作队列处理函数
void tx_work_handler(struct work_struct *work)
{
    struct my_net_device *dev = container_of(work, struct my_net_device, tx_work);
    
    // 处理发送队列
    spin_lock_bh(&dev->lock);
    process_tx_queue(dev);
    spin_unlock_bh(&dev->lock);
}

// 网络设备打开函数
int netdev_open(struct net_device *ndev)
{
    struct my_net_device *dev = netdev_priv(ndev);
    int ret;
    
    // 初始化NAPI
    netif_napi_add(ndev, &dev->napi, my_napi_poll, 64);
    
    // 注册中断处理程序
    ret = request_irq(dev->irq, netdev_interrupt_handler,
                     IRQF_SHARED, ndev->name, dev);
    if (ret) {
        netif_napi_del(&dev->napi);
        return ret;
    }
    
    // 初始化工作队列
    INIT_WORK(&dev->tx_work, tx_work_handler);
    
    // 启动设备
    netif_start_queue(ndev);
    
    return 0;
}

6.2 中断处理的状态管理

#include <linux/interrupt.h>
#include <linux/bitops.h>

struct my_device {
    int irq;
    unsigned long status;
    spinlock_t lock;
    wait_queue_head_t wait_queue;
};

#define STATUS_IRQ_ENABLED   0
#define STATUS_IRQ_PENDING   1
#define STATUS_DEVICE_READY  2

static irqreturn_t stateful_interrupt_handler(int irq, void *dev_id)
{
    struct my_device *dev = dev_id;
    unsigned long flags;
    
    spin_lock_irqsave(&dev->lock, flags);
    
    // 标记中断待处理
    set_bit(STATUS_IRQ_PENDING, &dev->status);
    
    // 处理硬件状态
    read_device_status(dev);
    
    // 唤醒等待进程
    wake_up_interruptible(&dev->wait_queue);
    
    spin_unlock_irqrestore(&dev->lock, flags);
    
    return IRQ_HANDLED;
}

// 使能/禁用中断
void enable_my_irq(struct my_device *dev)
{
    unsigned long flags;
    
    spin_lock_irqsave(&dev->lock, flags);
    if (!test_and_set_bit(STATUS_IRQ_ENABLED, &dev->status)) {
        enable_irq(dev->irq);
    }
    spin_unlock_irqrestore(&dev->lock, flags);
}

void disable_my_irq(struct my_device *dev)
{
    unsigned long flags;
    
    spin_lock_irqsave(&dev->lock, flags);
    if (test_and_clear_bit(STATUS_IRQ_ENABLED, &dev->status)) {
        disable_irq(dev->irq);
    }
    spin_unlock_irqrestore(&dev->lock, flags);
}

7. 中断统计和调试

7.1 中断统计信息

#include <linux/interrupt.h>
#include <linux/proc_fs.h>

// 查看系统中断统计
static int __init check_interrupts(void)
{
    int i;
    
    printk(KERN_INFO "Interrupt statistics:\n");
    
    for (i = 0; i < NR_IRQS; i++) {
        struct irq_desc *desc = irq_to_desc(i);
        if (desc && desc->action) {
            printk(KERN_INFO "IRQ %d: %s, count: %lu\n",
                   i, desc->action->name, kstat_irqs(i));
        }
    }
    
    return 0;
}

// 在/proc/interrupts中的自定义信息
static int proc_interrupts_show(struct seq_file *m, void *v)
{
    int i;
    
    seq_printf(m, "%-16s %10s %10s %-20s\n", 
               "IRQ", "COUNT", "DEVICE", "TYPE");
    
    for (i = 0; i < NR_IRQS; i++) {
        struct irq_desc *desc = irq_to_desc(i);
        if (desc && desc->action) {
            seq_printf(m, "%-16d %10lu %10s %-20s\n",
                       i, kstat_irqs(i),
                       desc->action->name, "edge");
        }
    }
    
    return 0;
}

7.2 调试技巧

// 中断调试宏
#define DEBUG_INTERRUPTS 1

#ifdef DEBUG_INTERRUPTS
#define IRQ_DEBUG(fmt, args...) \
    printk(KERN_DEBUG "IRQ_DEBUG: " fmt, ##args)
#else
#define IRQ_DEBUG(fmt, args...) 
#endif

static irqreturn_t debug_interrupt_handler(int irq, void *dev_id)
{
    IRQ_DEBUG("Entering interrupt handler for IRQ %d\n", irq);
    
    // 记录进入时间
    unsigned long start_jiffies = jiffies;
    
    // 中断处理...
    
    // 记录处理时间
    unsigned long duration = jiffies - start_jiffies;
    if (duration > HZ/100) {  // 超过10ms
        printk(KERN_WARNING "Interrupt handler for IRQ %d took %lu jiffies\n",
               irq, duration);
    }
    
    IRQ_DEBUG("Exiting interrupt handler for IRQ %d\n", irq);
    
    return IRQ_HANDLED;
}

8. 最佳实践

8.1 中断处理准则

// 1. 保持中断处理程序简短
static irqreturn_t good_handler(int irq, void *dev_id)
{
    // 只做必要的最小工作
    acknowledge_interrupt();
    read_critical_data();
    
    // 调度底半部处理其他工作
    schedule_work(&my_work);
    
    return IRQ_HANDLED;
}

// 2. 使用适当的锁
static irqreturn_t handler_with_lock(int irq, void *dev_id)
{
    struct my_device *dev = dev_id;
    unsigned long flags;
    
    // 使用自旋锁保护共享数据
    spin_lock_irqsave(&dev->lock, flags);
    
    // 访问共享数据
    dev->interrupt_count++;
    
    spin_unlock_irqrestore(&dev->lock, flags);
    
    return IRQ_HANDLED;
}

// 3. 正确处理错误
static int __init robust_irq_init(void)
{
    int ret;
    
    ret = request_irq(irq, handler, flags, name, dev);
    if (ret == -EBUSY) {
        printk(KERN_ERR "IRQ %d is busy\n", irq);
        // 尝试其他IRQ或共享
    } else if (ret == -EINVAL) {
        printk(KERN_ERR "Invalid IRQ parameters\n");
    } else if (ret) {
        printk(KERN_ERR "Failed to request IRQ: %d\n", ret);
    }
    
    return ret;
}

// 4. 资源清理
static void __exit cleanup_irq(void)
{
    // 确保所有底半部处理完成
    flush_workqueue(my_wq);
    tasklet_kill(&my_tasklet);
    
    // 释放中断
    free_irq(irq_number, dev_id);
    
    // 销毁工作队列
    destroy_workqueue(my_wq);
}

8.2 性能优化

// 1. 使用NAPI处理网络中断
// 2. 合理选择底半部机制
//   - 短时间任务:tasklet
//   - 可能睡眠的任务:工作队列
//   - 网络设备:NAPI

// 3. 中断亲和性(设置CPU亲和性)
static void set_irq_affinity(int irq, int cpu)
{
    cpumask_t mask;
    
    cpumask_clear(&mask);
    cpumask_set_cpu(cpu, &mask);
    
    if (irq_set_affinity(irq, &mask)) {
        printk(KERN_WARNING "Failed to set IRQ affinity\n");
    }
}

// 4. 避免中断风暴
static irqreturn_t rate_limited_handler(int irq, void *dev_id)
{
    static unsigned long last_time = 0;
    unsigned long now = jiffies;
    
    // 限制中断频率(最少间隔10ms)
    if (time_before(now, last_time + HZ/100)) {
        return IRQ_HANDLED;
    }
    last_time = now;
    
    // 正常中断处理...
    return IRQ_HANDLED;
}

中断处理是Linux设备驱动开发中的核心内容。正确的中断处理需要考虑性能、并发、资源管理等多个方面。通过合理使用顶半部和底半部机制,可以创建高效可靠的中断处理程序。