当前位置:首页>Linux>中断处理与并发控制:Linux驱动开发的核心难题破解

中断处理与并发控制:Linux驱动开发的核心难题破解

  • 2026-09-04 13:40:56
中断处理与并发控制:Linux驱动开发的核心难题破解

一、顶级架构一句话总结

硬件中断 → 中断控制器 → CPU响应 → 中断处理程序 → 唤醒进程/处理数据

中断机制解决了CPU与外设速度不匹配的问题,让外设能主动"通知"CPU处理任务,而不是CPU一直轮询等待。


二、为什么需要中断?

轮询 vs 中断

轮询方式:┌─────────────────────────────────────────┐│ while(1) {                              ││     if (device_ready) {                 ││         process_data();                 ││     }                                   ││ }                                       │└─────────────────────────────────────────┘问题:CPU空转,资源浪费严重中断方式:┌─────────────────────────────────────────┐│ 设备就绪 → 触发中断信号                  ││     ↓                                   ││ CPU暂停当前任务,执行中断处理程序         ││     ↓                                   ││ 处理完成,恢复原任务                     │└─────────────────────────────────────────┘优势:CPU高效利用,响应及时

中断的价值

指标
轮询方式
中断方式
CPU利用率
极低(大部分时间空转)
高(按需响应)
响应延迟
不确定
确定性高
功耗
高
低
系统负载
高
低

三、中断处理核心概念

1. 中断号(IRQ)

每个中断源都有唯一的中断号,内核通过中断号识别中断来源。

// 常见中断类型IRQ_TYPE_EDGE_RISING    // 上升沿触发IRQ_TYPE_EDGE_FALLING   // 下降沿触发IRQ_TYPE_EDGE_BOTH      // 双边沿触发IRQ_TYPE_LEVEL_HIGH     // 高电平触发IRQ_TYPE_LEVEL_LOW      // 低电平触发

2. 中断处理流程

硬件中断发生    ↓中断控制器(GIC/APIC)    ↓CPU响应中断    ↓保存当前上下文    ↓查找中断向量表    ↓执行中断处理函数(ISR)    ↓恢复上下文    ↓返回被中断的程序

3. 中断处理原则

•快进快出:中断处理程序必须尽可能短•不能睡眠:中断上下文不能调用可能睡眠的函数•不能访问用户空间:中断上下文没有进程上下文


四、中断处理程序开发

1. 注册中断处理函数

#include<linux/interrupt.h>// 中断处理函数原型irqreturn_t (*irq_handler_t)(int irq, void *dev_id);// 注册中断intrequest_irq(unsignedint irq,               // 中断号                irq_handler_t handler,          // 处理函数                unsigned long flags,            // 标志位                const char *name,               // 设备名称                void *dev_id);                  // 设备ID// 释放中断voidfree_irq(unsignedint irq, void *dev_id);

2. 中断标志位

// 常用标志位IRQF_TRIGGER_RISING    // 上升沿触发IRQF_TRIGGER_FALLING   // 下降沿触发IRQF_TRIGGER_HIGH      // 高电平触发IRQF_TRIGGER_LOW       // 低电平触发IRQF_SHARED            // 共享中断IRQF_DISABLED          // 禁用其他中断(已废弃)IRQF_NOBALANCING       // 禁止IRQ亲和性平衡

3. 中断处理函数返回值

// 返回值说明IRQ_HANDLED    // 中断已处理IRQ_NONE       // 中断未处理(用于共享中断判断)IRQ_WAKE_THREAD // 唤醒中断线程

4. 基本中断处理示例

#include<linux/module.h>#include<linux/interrupt.h>#include<linux/gpio.h>#define GPIO_IRQ_PIN 17staticirqreturn_tgpio_irq_handler(int irq, void *dev_id){    int gpio_value;    // 读取GPIO状态    gpio_value = gpio_get_value(GPIO_IRQ_PIN);    pr_info("GPIO interrupt triggered, value = %d\n", gpio_value);    return IRQ_HANDLED;}static int irq_number;staticint __init my_init(void){    int ret;    // 申请GPIO    ret = gpio_request(GPIO_IRQ_PIN, "irq_gpio");    if (ret) {        pr_err("Failed to request GPIO\n");        return ret;    }    // 设置GPIO为输入    gpio_direction_input(GPIO_IRQ_PIN);    // 获取中断号    irq_number = gpio_to_irq(GPIO_IRQ_PIN);    // 注册中断处理函数    ret = request_irq(irq_number,                      gpio_irq_handler,                      IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,                      "my_gpio_irq",                      NULL);    if (ret) {        pr_err("Failed to request IRQ\n");        gpio_free(GPIO_IRQ_PIN);        return ret;    }    pr_info("Module loaded, IRQ %d registered\n", irq_number);    return 0;}staticvoid __exit my_exit(void){    free_irq(irq_number, NULL);    gpio_free(GPIO_IRQ_PIN);    pr_info("Module unloaded\n");}module_init(my_init);module_exit(my_exit);MODULE_LICENSE("GPL");

五、中断下半部机制

为什么需要下半部?

中断处理程序运行在中断上下文,有以下限制:

•不能睡眠•不能执行耗时操作•不能访问用户空间

解决方案:将工作分为上半部(中断处理程序)和下半部(延迟处理)。

下半部机制对比

┌────────────────────────────────────────────────────────────┐│                    下半部机制对比                           │├──────────────┬─────────────┬─────────────┬────────────────┤│     机制      │   执行上下文 │   可睡眠?   │     适用场景   │├──────────────┼─────────────┼─────────────┼────────────────┤│  SoftIRQ     │   中断上下文 │     否      │   高性能网络   ││  Tasklet     │   中断上下文 │     否      │   一般设备驱动 ││  Workqueue   │   进程上下文 │     是      │   复杂耗时操作 ││  Threaded IRQ│   进程上下文 │     是      │   需要睡眠的中断│└──────────────┴─────────────┴─────────────┴────────────────┘

1. Tasklet

#include<linux/interrupt.h>// 定义taskletDECLARE_TASKLET(my_tasklet, tasklet_handler, (unsigned long)data);// 或者动态创建struct tasklet_struct my_tasklet;voidtasklet_handler(unsignedlong data){    // 处理逻辑    pr_info("Tasklet executed with data: %lu\n", data);}// 初始化tasklet_init(&my_tasklet, tasklet_handler, (unsigned long)my_data);// 调度执行tasklet_schedule(&my_tasklet);// 禁用/启用tasklet_disable(&my_tasklet);tasklet_enable(&my_tasklet);// 杀死(确保不再执行)tasklet_kill(&my_tasklet);

2. Workqueue

#include<linux/workqueue.h>// 静态定义staticDECLARE_WORK(my_work, work_handler);// 动态创建struct work_struct my_work;voidwork_handler(struct work_struct *work){    // 可以睡眠!    msleep(100);    pr_info("Work executed\n");}// 初始化INIT_WORK(&my_work, work_handler);// 调度执行schedule_work(&my_work);// 延迟调度schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(1000));// 等待工作完成flush_work(&my_work);flush_scheduled_work();// 取消工作cancel_work_sync(&my_work);

3. Threaded IRQ(推荐)

#include<linux/interrupt.h>// 上半部:快速响应irqreturn_tirq_thread_fn(int irq, void *dev_id){    struct my_device *dev = dev_id;    // 可以睡眠!    process_data(dev);    return IRQ_HANDLED;}// 下半部:线程化处理irqreturn_tirq_handler(int irq, void *dev_id){    // 快速响应    disable_irq_nosync(irq);    // 返回IRQ_WAKE_THREAD唤醒线程    return IRQ_WAKE_THREAD;}// 注册线程化中断ret = request_threaded_irq(irq_number,                           irq_handler,      // 上半部                           irq_thread_fn,    // 下半部线程                           IRQF_TRIGGER_RISING,                           "my_threaded_irq",                           dev_data);

六、并发控制机制

为什么需要并发控制?

在驱动开发中,多个执行流可能同时访问共享资源:

┌─────────────────────────────────────────────────────────┐│                  并发访问场景                           │├─────────────────────────────────────────────────────────┤│  进程A (read)     进程B (write)    中断处理程序         ││       ↓               ↓                 ↓              ││  ┌─────────────────────────────────────────┐           ││  │           共享数据(设备缓冲区)          │           ││  └─────────────────────────────────────────┘           ││            ↑ 可能导致数据竞争!                         │└─────────────────────────────────────────────────────────┘

并发控制机制对比

机制
上下文
可睡眠
开销
适用场景
原子操作
任意
否
最低
简单计数器
自旋锁
任意
否
低
短临界区
互斥锁
进程
是
中
长临界区
信号量
进程
是
高
资源计数
读写锁
任意
否
中
读多写少
RCU
进程
否
低
读多写少

1. 原子操作

#include<linux/atomic.h>// 原子变量定义atomic_t counter = ATOMIC_INIT(0);// 原子操作atomic_set(&counter, 10);           // 设置值int val = atomic_read(&counter);    // 读取值atomic_add(5, &counter);            // 加法atomic_sub(3, &counter);            // 减法atomic_inc(&counter);               // 自增atomic_dec(&counter);               // 自减// 带返回值的操作int new_val = atomic_add_return(5, &counter);int old_val = atomic_xchg(&counter, 20);  // 交换// 条件操作if (atomic_cmpxchg(&counter, 10, 20) == 10) {    // 原值是10,已替换为20}// 原子位操作unsigned long flags;set_bit(0, &flags);      // 设置位clear_bit(0, &flags);    // 清除位change_bit(0, &flags);   // 翻转位test_bit(0, &flags);     // 测试位test_and_set_bit(0, &flags);  // 测试并设置

2. 自旋锁

#include<linux/spinlock.h>// 定义自旋锁spinlock_t my_lock;// 初始化spin_lock_init(&my_lock);// 基本使用spin_lock(&my_lock);// 临界区代码spin_unlock(&my_lock);// 在中断处理中使用spin_lock_irqsave(&my_lock, flags);// 临界区代码spin_unlock_irqrestore(&my_lock, flags);// 在底半部中使用spin_lock_bh(&my_lock);// 临界区代码spin_unlock_bh(&my_lock);// 尝试获取锁(非阻塞)if (spin_trylock(&my_lock)) {    // 获取成功    spin_unlock(&my_lock);} else {    // 获取失败}

3. 互斥锁

#include<linux/mutex.h>// 定义互斥锁DEFINE_MUTEX(my_mutex);// 或struct mutex my_mutex;mutex_init(&my_mutex);// 基本使用mutex_lock(&my_mutex);// 临界区代码(可以睡眠)mutex_unlock(&my_mutex);// 非阻塞获取if (mutex_trylock(&my_mutex)) {    // 获取成功    mutex_unlock(&my_mutex);} else {    // 获取失败}// 带中断的获取if (mutex_lock_interruptible(&my_mutex)) {    // 被信号中断    return -ERESTARTSYS;}// 临界区代码mutex_unlock(&my_mutex);// 带超时的获取if (mutex_lock_killable(&my_mutex)) {    // 被杀死信号中断    return -ERESTARTSYS;}

4. 信号量

#include<linux/semaphore.h>// 定义信号量DEFINE_SEMAPHORE(my_sem);  // 初始值为1(互斥)// 或struct semaphore my_sem;sema_init(&my_sem, 1);     // 初始值为1// 获取信号量(可睡眠)down(&my_sem);// 临界区代码up(&my_sem);// 可中断获取if (down_interruptible(&my_sem)) {    return -ERESTARTSYS;}// 临界区代码up(&my_sem);// 非阻塞获取if (down_trylock(&my_sem)) {    // 获取失败    return -EBUSY;}// 临界区代码up(&my_sem);

5. 读写锁

#include<linux/rwlock.h>// 定义读写锁rwlock_t my_rwlock;rwlock_init(&my_rwlock);// 读操作read_lock(&my_rwlock);// 读临界区read_unlock(&my_rwlock);// 写操作write_lock(&my_rwlock);// 写临界区write_unlock(&my_rwlock);// 带中断保护的版本read_lock_irqsave(&my_rwlock, flags);read_unlock_irqrestore(&my_rwlock, flags);write_lock_irqsave(&my_rwlock, flags);write_unlock_irqrestore(&my_rwlock, flags);

七、完整示例:中断+并发控制

#include<linux/module.h>#include<linux/interrupt.h>#include<linux/gpio.h>#include<linux/workqueue.h>#include<linux/mutex.h>#include<linux/cdev.h>#include<linux/fs.h>#include<linux/uaccess.h>#define DEVICE_NAME "irq_dev"#define GPIO_PIN 17#define BUFFER_SIZE 256struct my_device {    struct cdev cdev;    struct mutex lock;    struct work_struct work;    char buffer[BUFFER_SIZE];    int data_ready;    int irq;    wait_queue_head_t wait_queue;};static struct my_device *my_dev;static int major_number;// 工作队列处理函数staticvoidwork_handler(struct work_struct *work){    struct my_device *dev = container_of(work, struct my_device, work);    mutex_lock(&dev->lock);    // 模拟数据处理    snprintf(dev->buffer, BUFFER_SIZE, "IRQ processed at %lu", jiffies);    dev->data_ready = 1;    mutex_unlock(&dev->lock);    // 唤醒等待的进程    wake_up_interruptible(&dev->wait_queue);    pr_info("Work handler completed\n");}// 中断处理函数(上半部)staticirqreturn_tirq_handler(int irq, void *dev_id){    struct my_device *dev = dev_id;    pr_info("IRQ triggered\n");    // 调度工作队列(下半部)    schedule_work(&dev->work);    return IRQ_HANDLED;}// 设备读取staticssize_tmy_read(struct file *file, char __user *buf,                       size_t count, loff_t *offset){    struct my_device *dev = file->private_data;    ssize_t ret;    // 等待数据就绪    if (wait_event_interruptible(dev->wait_queue, dev->data_ready))        return -ERESTARTSYS;    mutex_lock(&dev->lock);    if (count > strlen(dev->buffer))        count = strlen(dev->buffer);    if (copy_to_user(buf, dev->buffer, count)) {        ret = -EFAULT;        goto out;    }    dev->data_ready = 0;    ret = count;out:    mutex_unlock(&dev->lock);    return ret;}// 设备打开staticintmy_open(struct inode *inode, struct file *file){    struct my_device *dev = container_of(inode->i_cdev, struct my_device, cdev);    file->private_data = dev;    return 0;}static struct file_operations fops = {    .owner = THIS_MODULE,    .open = my_open,    .read = my_read,};staticint __init my_init(void){    dev_t dev;    int ret;    // 分配设备结构    my_dev = kzalloc(sizeof(*my_dev), GFP_KERNEL);    if (!my_dev)        return -ENOMEM;    // 初始化互斥锁    mutex_init(&my_dev->lock);    // 初始化等待队列    init_waitqueue_head(&my_dev->wait_queue);    // 初始化工作队列    INIT_WORK(&my_dev->work, work_handler);    // 申请GPIO    ret = gpio_request(GPIO_PIN, "irq_gpio");    if (ret)        goto err_gpio;    gpio_direction_input(GPIO_PIN);    my_dev->irq = gpio_to_irq(GPIO_PIN);    // 注册中断    ret = request_irq(my_dev->irq, irq_handler,                      IRQF_TRIGGER_RISING,                      "my_irq", my_dev);    if (ret)        goto err_irq;    // 注册字符设备    ret = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);    if (ret)        goto err_chrdev;    major_number = MAJOR(dev);    cdev_init(&my_dev->cdev, &fops);    my_dev->cdev.owner = THIS_MODULE;    ret = cdev_add(&my_dev->cdev, dev, 1);    if (ret)        goto err_cdev;    pr_info("Module loaded, major = %d\n", major_number);    return 0;err_cdev:    unregister_chrdev_region(dev, 1);err_chrdev:    free_irq(my_dev->irq, my_dev);err_irq:    gpio_free(GPIO_PIN);err_gpio:    kfree(my_dev);    return ret;}staticvoid __exit my_exit(void){    dev_t dev = MKDEV(major_number, 0);    cancel_work_sync(&my_dev->work);    cdev_del(&my_dev->cdev);    unregister_chrdev_region(dev, 1);    free_irq(my_dev->irq, my_dev);    gpio_free(GPIO_PIN);    kfree(my_dev);    pr_info("Module unloaded\n");}module_init(my_init);module_exit(my_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Driver Developer");MODULE_DESCRIPTION("Interrupt and Concurrency Demo");

八、选择指南

如何选择并发控制机制?

┌─────────────────────────────────────────────────────────────┐│                    并发控制选择决策树                        │├─────────────────────────────────────────────────────────────┤│                                                             ││  临界区是否可能睡眠?                                        ││     │                                                       ││     ├── 否 → 使用自旋锁                                     ││     │         │                                             ││     │         ├── 在中断上下文?→ spin_lock_irqsave         ││     │         └── 在进程上下文?→ spin_lock                 ││     │                                                       ││     └── 是 → 使用互斥锁                                     ││               │                                             ││               ├── 需要资源计数?→ 信号量                    ││               └── 只需互斥?→ mutex                         ││                                                             ││  只是简单计数?→ 原子操作                                   ││                                                             ││  读多写少?→ 读写锁/RCU                                     ││                                                             │└─────────────────────────────────────────────────────────────┘

如何选择下半部机制?

需要睡眠? → Workqueue / Threaded IRQ    │    └── 否 → 性能要求极高? → SoftIRQ              │              └── 否 → Tasklet(推荐)

九、终极总结

中断处理 + 并发控制 = Linux驱动开发的核心难题

•中断处理:上半部快速响应,下半部延迟处理•下半部机制:Tasklet(简单)、Workqueue(可睡眠)、Threaded IRQ(推荐)•并发控制:自旋锁(短临界区)、互斥锁(可睡眠)、原子操作(简单计数)•选择原则:根据上下文、是否睡眠、性能需求综合选择

掌握中断与并发控制,是成为高级驱动开发工程师的必经之路!

最新文章

随机文章