Linux 驱动中断

88 阅读18分钟

Linux 驱动中断深度剖析:从 GIC 控制器到工作队列 在 Linux 驱动开发领域,中断机制是实现设备高效响应与数据交互的核心。从底层硬件中断控制器,到上层软件处理逻辑,整个中断处理体系涉及众多关键技术。本文将围绕 GIC 控制器、GPIO 中断、Tasklet、软中断、工作队列等核心内容,深入解析 Linux 驱动中断的工作原理与实践应用。

一、GIC 控制器:硬件中断的枢纽

GIC(Generic Interrupt Controller)即通用中断控制器,是 ARM 架构中负责管理和分发中断请求的关键组件 ,它的出现,让中断管理变得更加高效和灵活。

核心功能: GIC 负责接收来自外部设备和内部模块的中断请求,对中断进行优先级判定,并将中断分发到对应的 CPU 核心进行处理。同时,它还支持中断的使能、屏蔽以及分组管理等功能。 架构演进:从早期的 GICv1 到如今广泛应用的 GICv3、GICv4,GIC 的功能不断增强。例如 GICv3 引入了虚拟化支持,能够更好地服务于虚拟化环境下的中断管理;GICv4 进一步优化了性能和安全性。 关键概念 中断类型: 包括 SGI(软件生成中断)、PPI(私有外设中断)和 SPI(共享外设中断)。SGI 由软件触发,常用于多核处理器间的通信;PPI 是每个 CPU 私有的中断源;SPI 则是多个 CPU 共享的外设中断。 中断分组: 中断可分为安全组和非安全组,分别对应不同的安全等级和访问权限,确保系统安全性。

二、GPIO 中断:设备交互的常见入口

GPIO(General - Purpose Input/Output)中断是 Linux 驱动开发中最常用的中断类型之一,常用于连接外部设备,如按键、传感器等。

配置流程 GPIO 引脚配置:首先要确定使用的 GPIO 引脚,并通过内核 API 将其配置为输入模式,例如使用gpio_direction_input函数。 中断号获取: 通过gpio_to_irq函数将 GPIO 引脚号转换为对应的中断号。 中断注册: 使用request_irq函数注册中断处理函数,同时指定中断触发方式,如上升沿触发(IRQF_TRIGGER_RISING)、下降沿触发(IRQF_TRIGGER_FALLING)或双边沿触发(IRQF_TRIGGER_BOTH)。 示例代码

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


#define DEVICE_NAME "irq_dev"
#define GPIO_NUM 15

static int irq;

// 中断处理函数
static irqreturn_t simple_interrupt(int irq, void *dev_id) {
	
    printk(KERN_INFO "simple_interrupt\n");
	
    return IRQ_HANDLED;
}

static int __init irq_driver_init(void) {
    int ret;
    
    
	irq = gpio_to_irq(GPIO_NUM);
    printk(KERN_INFO "irq_driver_init irq = %d\n", irq);
	ret = request_irq(irq, simple_interrupt, 
                     IRQF_TRIGGER_HIGH,
                     "simple_irq", &irq);
	
	
    printk(KERN_INFO "irq_driver_init ret = %d\n", ret);
    return 0;
}

static void __exit irq_driver_exit(void) {
	free_irq(irq, NULL);
    printk(KERN_INFO "irq_driver_exit\n");
}

module_init(irq_driver_init);
module_exit(irq_driver_exit);


MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using irq");
MODULE_AUTHOR("cmy");

三、Tasklet 与软中断:高效的异步处理机制

3.1 Tasklet

Tasklet 是基于软中断实现的轻量级异步处理机制,它在中断处理中扮演着重要角色。

特点: Tasklet 运行在软中断上下文,具有以下特点: 不可睡眠: 由于运行在中断上下文,Tasklet 中不能使用会导致睡眠的函数,否则会引发内核错误。 快速执行: 设计初衷是快速处理中断相关的次要任务,避免阻塞中断处理流程。 使用方法 定义 Tasklet: 通过DECLARE_TASKLET宏定义一个 Tasklet,例如DECLARE_TASKLET(my_tasklet, my_tasklet_handler, data),其中my_tasklet_handler是处理函数,data是传递给处理函数的参数。 调度 Tasklet: 在需要执行 Tasklet 时,使用tasklet_schedule函数进行调度,它会将 Tasklet 加入到调度队列中,等待合适的时机执行。 使用示例

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

#define GPIO_PIN 15

static struct tasklet_struct my_tasklet;
static unsigned int irq_count = 0;

// Tasklet 处理函数(运行在软中断上下文)
static void tasklet_handler(unsigned long data) {
    // 获取传递的参数
    int gpio = (int)data;
    
    // 模拟耗时操作(但不能睡眠!)
    //msleep(1); // 错误!Tasklet 中禁止睡眠
    
    // 正确做法:使用忙等待或简化操作
    //udelay(1000); // 短时间延迟(微秒级)
    
    printk(KERN_INFO "Tasklet: GPIO %d interrupt count = %u\n", 
           gpio, irq_count);
}

// GPIO 中断处理函数(顶半部)
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    // 增加中断计数
    irq_count++;
    
    // 调度 Tasklet(底半部处理)
    tasklet_schedule(&my_tasklet);
    
    return IRQ_HANDLED;
}

static int __init tasklet_example_init(void) {
    int irq;
    int ret;
    
    // 初始化 Tasklet
    tasklet_init(&my_tasklet, tasklet_handler, (unsigned long)GPIO_PIN);
    
    // 配置 GPIO 并请求中断
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    irq = gpio_to_irq(GPIO_PIN);
    ret = request_irq(irq, gpio_irq_handler, 
                     IRQF_TRIGGER_FALLING, "my_gpio_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    printk(KERN_INFO "Tasklet example initialized\n");
    return 0;
}

static void __exit tasklet_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 确保 Tasklet 不再执行
    tasklet_kill(&my_tasklet);
    
    // 释放中断和 GPIO
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    
    printk(KERN_INFO "Tasklet example exited\n");
}

module_init(tasklet_example_init);
module_exit(tasklet_example_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using tasklet");
MODULE_AUTHOR("cmy");

3.2 软中断

软中断是 Linux 内核中一种重要的中断处理机制,为系统提供了高效的异步处理能力。

实现原理: 软中断通过softirq_vec数组来管理不同类型的软中断处理函数,每个元素对应一种软中断类型。系统在适当的时机,如硬件中断处理结束后,会检查并执行已注册的软中断。 常见类型: 包括网络接收软中断(NET_RX_SOFTIRQ)、网络发送软中断(NET_TX_SOFTIRQ)、定时器软中断(TIMER_SOFTIRQ)等。开发者也可以自定义软中断类型,但需要谨慎处理,确保不会影响系统稳定性。 使用示例

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

#define GPIO_PIN 15

static unsigned int irq_count = 0;
static struct work_struct my_work;

// 自定义软中断处理函数
static void my_softirq_handler(struct softirq_action *action) {
    // 获取当前 GPIO 值
    int value = gpio_get_value(GPIO_PIN);
    
    // 处理数据(示例:打印信息)
    printk(KERN_INFO "Softirq: GPIO %d value = %d, count = %u\n", 
           GPIO_PIN, value, irq_count);
    
    // 调度工作队列(如果需要睡眠操作)
    schedule_work(&my_work);
}

// 工作队列处理函数(可以睡眠)
static void work_handler(struct work_struct *work) {
    // 模拟耗时操作
    //msleep(10); // 可以睡眠
    
    printk(KERN_INFO "Work queue: Processing completed\n");
}

// 软中断注册和初始化
static struct softirq_action my_softirq_action = {
    .action = my_softirq_handler,
};

// GPIO 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    // 增加中断计数
    irq_count++;
    
    // 触发软中断
    raise_softirq(HI_SOFTIRQ); // 使用高优先级软中断
    
    return IRQ_HANDLED;
}

static int __init softirq_example_init(void) {
    int irq;
    int ret;
    
    // 注册软中断
    open_softirq(HI_SOFTIRQ, my_softirq_handler);
    
    // 初始化工作队列
    INIT_WORK(&my_work, work_handler);
    
    // 配置 GPIO 并请求中断
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    irq = gpio_to_irq(GPIO_PIN);
    ret = request_irq(irq, gpio_irq_handler, 
                     IRQF_TRIGGER_FALLING, "my_gpio_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    printk(KERN_INFO "Softirq example initialized\n");
    return 0;
}

static void __exit softirq_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 释放资源
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    
    // 清理软中断
    raise_softirq_irqoff(HI_SOFTIRQ);
    
    // 取消未完成的工作
    cancel_work_sync(&my_work);
    
    printk(KERN_INFO "Softirq example exited\n");
}


module_init(softirq_example_init);
module_exit(softirq_example_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using softirq");
MODULE_AUTHOR("cmy");

四、工作队列:灵活的任务处理框架

工作队列是 Linux 内核提供的一种用于在进程上下文执行任务的机制,相比 Tasklet 和软中断,它更加灵活,支持睡眠操作。

4.1 共享工作队列

共享工作队列是内核预定义的工作队列,所有驱动都可以使用。

优点: 无需额外创建和销毁工作队列,减少了资源开销,使用方便。 使用示例

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

#define GPIO_PIN 17

static struct work_struct my_work;
static unsigned int irq_count = 0;

// 工作队列处理函数
static void work_handler(struct work_struct *work) {
    // 获取当前 GPIO 值
    int value = gpio_get_value(GPIO_PIN);
    
    // 模拟耗时操作(可以睡眠)
    //msleep(20);
    
    printk(KERN_INFO "Shared workqueue: GPIO %d value = %d, count = %u\n", 
           GPIO_PIN, value, irq_count);
}

// GPIO 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    // 增加中断计数
    irq_count++;
    
    // 调度共享工作队列
    schedule_work(&my_work);
    
    return IRQ_HANDLED;
}

static int __init shared_wq_example_init(void) {
    int irq;
    int ret;
    
    // 初始化工作项
    INIT_WORK(&my_work, work_handler);
    
    // 配置 GPIO 并请求中断
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    irq = gpio_to_irq(GPIO_PIN);
    ret = request_irq(irq, gpio_irq_handler, 
                     IRQF_TRIGGER_FALLING, "my_gpio_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    printk(KERN_INFO "Shared workqueue example initialized\n");
    return 0;
}

static void __exit shared_wq_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 取消未完成的工作
    cancel_work_sync(&my_work);
    
    // 释放资源
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    
    printk(KERN_INFO "Shared workqueue example exited\n");
}

module_init(shared_wq_example_init);
module_exit(shared_wq_example_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using shared workqueue");
MODULE_AUTHOR("cmy");

4.2 自定义工作队列

自定义工作队列允许开发者根据具体需求创建独立的工作队列。

创建与销毁:使用create_workqueue函数创建工作队列,例如struct workqueue_struct *my_wq = create_workqueue("my_workqueue");使用destroy_workqueue函数销毁工作队列。 优点:可以为工作队列设置特定的属性,如优先级、是否允许并发执行等,满足不同的业务场景需求。 使用示例

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

#define GPIO_PIN 17

static struct workqueue_struct *my_wq;
static struct work_struct my_work;
static unsigned int irq_count = 0;

// 工作队列处理函数
static void work_handler(struct work_struct *work) {
    // 获取当前 GPIO 值
    int value = gpio_get_value(GPIO_PIN);
    
    // 模拟耗时操作
    //msleep(50);
    
    printk(KERN_INFO "Custom workqueue: GPIO %d value = %d, count = %u\n", 
           GPIO_PIN, value, irq_count);
}

// GPIO 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    // 增加中断计数
    irq_count++;
    
    // 调度自定义工作队列
    queue_work(my_wq, &my_work);
    
    return IRQ_HANDLED;
}

static int __init custom_wq_example_init(void) {
    int irq;
    int ret;
    
    // 创建自定义工作队列
    my_wq = create_workqueue("my_custom_wq");
    if (!my_wq) {
        printk(KERN_ERR "Failed to create workqueue\n");
        return -ENOMEM;
    }
    
    // 初始化工作项
    INIT_WORK(&my_work, work_handler);
    
    // 配置 GPIO 并请求中断
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    irq = gpio_to_irq(GPIO_PIN);
    ret = request_irq(irq, gpio_irq_handler, 
                     IRQF_TRIGGER_FALLING, "my_gpio_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    printk(KERN_INFO "Custom workqueue example initialized\n");
    return 0;
}

static void __exit custom_wq_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 取消未完成的工作并销毁工作队列
    cancel_work_sync(&my_work);
    flush_workqueue(my_wq);
    destroy_workqueue(my_wq);
    
    // 释放资源
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    
    printk(KERN_INFO "Custom workqueue example exited\n");
}

module_init(custom_wq_example_init);
module_exit(custom_wq_example_exit);


MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using custom workqueue");
MODULE_AUTHOR("cmy");

4.3 延迟工作

延迟工作是指在一定时间延迟后再执行的工作项。

实现方式:通过schedule_delayed_work函数来调度延迟工作。例如schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(1000)),表示将my_delayed_work工作项在 1 秒后执行。

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

#define GPIO_PIN 17
#define DELAY_MS 1000  // 延迟1秒

static struct delayed_work my_delayed_work;
static unsigned int irq_count = 0;

// 延迟工作处理函数
static void delayed_work_handler(struct work_struct *work) {
    // 获取当前 GPIO 值
    int value = gpio_get_value(GPIO_PIN);
    
    printk(KERN_INFO "Delayed work: GPIO %d value = %d, count = %u (delayed %d ms)\n", 
           GPIO_PIN, value, irq_count, DELAY_MS);
}

// GPIO 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    // 增加中断计数
    irq_count++;
    
    // 调度延迟工作(注意使用专门的 API)
    schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(DELAY_MS));
    
    return IRQ_HANDLED;
}

static int __init delayed_work_example_init(void) {
    int irq;
    int ret;
    
    // 初始化延迟工作
    INIT_DELAYED_WORK(&my_delayed_work, delayed_work_handler);
    
    // 配置 GPIO 并请求中断
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    irq = gpio_to_irq(GPIO_PIN);
    ret = request_irq(irq, gpio_irq_handler, 
                     IRQF_TRIGGER_FALLING, "my_gpio_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    printk(KERN_INFO "Delayed work example initialized (delay = %d ms)\n", DELAY_MS);
    return 0;
}

static void __exit delayed_work_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 取消未完成的延迟工作
    cancel_delayed_work_sync(&my_delayed_work);
    
    // 释放资源
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    
    printk(KERN_INFO "Delayed work example exited\n");
}

module_init(delayed_work_example_init);
module_exit(delayed_work_example_exit);



MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using delayed work");
MODULE_AUTHOR("cmy");

4.4 工作队列传参

在工作处理函数中传递参数可以通过以下两种方式实现:

结构体封装:定义一个包含参数的结构体,将结构体指针作为工作项的参数传递给工作处理函数。 使用container_of宏:在工作处理函数中,通过container_of宏从struct work_struct指针获取包含参数的结构体指针。 使用示例

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

#define GPIO_PIN 17

// 自定义数据结构(用于传递参数)
struct my_data {
    int gpio;
    unsigned int count;
    struct work_struct work;
};

static struct my_data *data;
static struct workqueue_struct *my_wq;

// 工作队列处理函数
static void work_handler(struct work_struct *work) {
    // 从 work_struct 指针获取自定义数据结构指针
    struct my_data *d = container_of(work, struct my_data, work);
    
    // 获取当前 GPIO 值
    int value = gpio_get_value(d->gpio);
    
    printk(KERN_INFO "Work with params: GPIO %d value = %d, count = %u\n", 
           d->gpio, value, d->count);
}

// GPIO 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    // 增加计数
    data->count++;
    
    // 调度工作队列
    queue_work(my_wq, &data->work);
    
    return IRQ_HANDLED;
}

static int __init work_with_params_example_init(void) {
    int irq;
    int ret;
    
    // 创建自定义工作队列
    my_wq = create_workqueue("my_param_wq");
    if (!my_wq) {
        printk(KERN_ERR "Failed to create workqueue\n");
        return -ENOMEM;
    }
    
    // 分配并初始化自定义数据结构
    data = kmalloc(sizeof(*data), GFP_KERNEL);
    if (!data) {
        printk(KERN_ERR "Failed to allocate memory\n");
        destroy_workqueue(my_wq);
        return -ENOMEM;
    }
    
    data->gpio = GPIO_PIN;
    data->count = 0;
    INIT_WORK(&data->work, work_handler);
    
    // 配置 GPIO 并请求中断
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        kfree(data);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        kfree(data);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    irq = gpio_to_irq(GPIO_PIN);
    ret = request_irq(irq, gpio_irq_handler, 
                     IRQF_TRIGGER_FALLING, "my_gpio_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        kfree(data);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    printk(KERN_INFO "Work with params example initialized\n");
    return 0;
}

static void __exit work_with_params_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 取消未完成的工作并销毁工作队列
    cancel_work_sync(&data->work);
    flush_workqueue(my_wq);
    destroy_workqueue(my_wq);
    
    // 释放资源
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    kfree(data);
    
    printk(KERN_INFO "Work with params example exited\n");
}

module_init(work_with_params_example_init);
module_exit(work_with_params_example_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using work with params");
MODULE_AUTHOR("cmy");

4.5 并发管理工作队列

在多任务环境下,可能会出现多个工作项同时访问共享资源的情况,此时需要进行并发管理。

互斥锁:使用mutex(互斥锁)来保护共享资源,确保同一时间只有一个工作项能够访问共享资源。 信号量:信号量也可以用于控制对共享资源的访问,相比互斥锁,它可以更灵活地控制资源的使用数量。 使用示例

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>

#define GPIO_PIN 17
#define SHARED_RESOURCE_SIZE 10

// 共享资源和保护锁
static int shared_resource[SHARED_RESOURCE_SIZE];
static DEFINE_MUTEX(resource_mutex);

static struct workqueue_struct *my_wq;
static struct work_struct work1, work2;
static unsigned int irq_count = 0;

// 工作队列处理函数1
static void work_handler1(struct work_struct *work) {
    // 获取锁保护共享资源
    mutex_lock(&resource_mutex);
    
    // 访问共享资源
    shared_resource[0]++;
    //msleep(10);  // 模拟耗时操作
    
    printk(KERN_INFO "Work1: shared_resource[0] = %d\n", shared_resource[0]);
    
    // 释放锁
    mutex_unlock(&resource_mutex);
}

// 工作队列处理函数2
static void work_handler2(struct work_struct *work) {
    // 获取锁保护共享资源
    mutex_lock(&resource_mutex);
    
    // 访问共享资源
    shared_resource[0]--;
    //msleep(10);  // 模拟耗时操作
    
    printk(KERN_INFO "Work2: shared_resource[0] = %d\n", shared_resource[0]);
    
    // 释放锁
    mutex_unlock(&resource_mutex);
}

// GPIO 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    // 增加中断计数
    irq_count++;
    
    // 交替调度两个工作项(模拟并发访问)
    if (irq_count % 2 == 0) {
        queue_work(my_wq, &work1);
    } else {
        queue_work(my_wq, &work2);
    }
    
    return IRQ_HANDLED;
}

static int __init concurrency_wq_example_init(void) {
    int irq;
    int ret;
    
    // 初始化共享资源
    memset(shared_resource, 0, sizeof(shared_resource));
    
    // 创建自定义工作队列
    my_wq = create_workqueue("my_concurrency_wq");
    if (!my_wq) {
        printk(KERN_ERR "Failed to create workqueue\n");
        return -ENOMEM;
    }
    
    // 初始化工作项
    INIT_WORK(&work1, work_handler1);
    INIT_WORK(&work2, work_handler2);
    
    // 配置 GPIO 并请求中断
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    irq = gpio_to_irq(GPIO_PIN);
    ret = request_irq(irq, gpio_irq_handler, 
                     IRQF_TRIGGER_FALLING, "my_gpio_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        destroy_workqueue(my_wq);
        return ret;
    }
    
    printk(KERN_INFO "Concurrency workqueue example initialized\n");
    return 0;
}

static void __exit concurrency_wq_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 取消未完成的工作并销毁工作队列
    cancel_work_sync(&work1);
    cancel_work_sync(&work2);
    flush_workqueue(my_wq);
    destroy_workqueue(my_wq);
    
    // 释放资源
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    
    printk(KERN_INFO "Concurrency workqueue example exited\n");
}

module_init(concurrency_wq_example_init);
module_exit(concurrency_wq_example_exit);


MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using concurrency workqueue");
MODULE_AUTHOR("cmy");

五、中断线程化:提升中断处理的灵活性

中断线程化是 Linux 内核提供的一种将中断处理函数运行在独立线程中的机制。

优势 可睡眠操作:中断线程运行在进程上下文,因此可以在中断处理函数中使用睡眠函数,方便处理一些耗时较长的任务,如等待资源、进行文件操作等。 更好的调度:由于运行在进程上下文,中断线程可以像普通进程一样参与系统调度,避免长时间占用 CPU,提高系统的整体性能和响应性。 使用方法:使用request_threaded_irq函数注册中断处理函数,该函数接受两个处理函数参数,一个是快速处理的顶半部函数(可以为NULL),另一个是运行在线程中的底半部函数。 使用示例

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

#define GPIO_PIN 17

static unsigned int irq_count = 0;

// 线程化中断处理函数(底半部,可睡眠)
static irqreturn_t threaded_irq_handler(int irq, void *dev_id) {
    // 增加中断计数
    irq_count++;
    
    // 模拟耗时操作(可以睡眠)
    msleep(50);
    
    // 获取当前 GPIO 值
    int value = gpio_get_value(GPIO_PIN);
    
    printk(KERN_INFO "Threaded IRQ handler: GPIO %d value = %d, count = %u\n", 
           GPIO_PIN, value, irq_count);
    
    return IRQ_HANDLED;
}

// 快速处理函数(顶半部,可选)
static irqreturn_t fast_irq_handler(int irq, void *dev_id) {
    // 快速处理紧急任务(如果有)
    
    // 返回 IRQ_WAKE_THREAD 以触发线程化处理函数
    return IRQ_WAKE_THREAD;
}

static int __init threaded_irq_example_init(void) {
    int irq;
    int ret;
    
    // 配置 GPIO
    ret = gpio_request(GPIO_PIN, "my_gpio");
    if (ret) {
        printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
        return ret;
    }
    
    ret = gpio_direction_input(GPIO_PIN);
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO %d as input\n", GPIO_PIN);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    // 获取中断号
    irq = gpio_to_irq(GPIO_PIN);
    
    // 请求线程化中断(注意最后一个参数传递 NULL)
    ret = request_threaded_irq(irq, fast_irq_handler, 
                             threaded_irq_handler,
                             IRQF_TRIGGER_FALLING, 
                             "my_threaded_irq", NULL);
    if (ret) {
        printk(KERN_ERR "Failed to request threaded IRQ %d\n", irq);
        gpio_free(GPIO_PIN);
        return ret;
    }
    
    printk(KERN_INFO "Threaded IRQ example initialized\n");
    return 0;
}

static void __exit threaded_irq_example_exit(void) {
    int irq = gpio_to_irq(GPIO_PIN);
    
    // 释放中断和 GPIO
    free_irq(irq, NULL);
    gpio_free(GPIO_PIN);
    
    printk(KERN_INFO "Threaded IRQ example exited\n");
}

module_init(threaded_irq_example_init);
module_exit(threaded_irq_example_exit);


MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple driver using Threaded IRQ");
MODULE_AUTHOR("cmy");

六、总结

Linux 驱动中断体系涵盖了从硬件到软件的多个层面,GIC 控制器作为硬件基础,为中断的接收和分发提供了支持;GPIO 中断则是驱动与外部设备交互的常用方式。而 Tasklet、软中断和工作队列等软件机制,为中断处理提供了不同的解决方案,满足了各种复杂场景的需求。中断线程化更是进一步提升了中断处理的灵活性和系统性能。深入理解和掌握这些知识,对于开发高效、稳定的 Linux 驱动程序至关重要。在实际开发中,开发者需要根据具体的应用场景,合理选择和运用这些中断处理机制,以实现最佳的系统性能和功能。