Linux中断
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设备驱动开发中的核心内容。正确的中断处理需要考虑性能、并发、资源管理等多个方面。通过合理使用顶半部和底半部机制,可以创建高效可靠的中断处理程序。
            本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 恒星不见
        
     评论
            
                匿名评论
                隐私政策
            
            
                你无需删除空行,直接评论以获取最佳展示效果
            
         
            
        
