【Linux 驱动中断】
Linux 驱动中断
- 一、GIC 控制器:硬件中断的枢纽
- 二、GPIO 中断:设备交互的常见入口
- 三、Tasklet 与软中断:高效的异步处理机制
- 3.1 Tasklet
- 3.2 软中断
- 四、工作队列:灵活的任务处理框架
- 4.1 共享工作队列
- 4.2 自定义工作队列
- 4.3 延迟工作
- 4.4 工作队列传参
- 4.5 并发管理工作队列
- 五、中断线程化:提升中断处理的灵活性
- 六、总结
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 15static 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 15static 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;// 初始化 Tasklettasklet_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);// 释放中断和 GPIOfree_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 15static 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 17static 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 17static 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 17static 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;// 配置 GPIOret = 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);// 释放中断和 GPIOfree_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 驱动程序至关重要。在实际开发中,开发者需要根据具体的应用场景,合理选择和运用这些中断处理机制,以实现最佳的系统性能和功能。
相关文章:
【Linux 驱动中断】
Linux 驱动中断 一、GIC 控制器:硬件中断的枢纽二、GPIO 中断:设备交互的常见入口三、Tasklet 与软中断:高效的异步处理机制3.1 Tasklet3.2 软中断 四、工作队列:灵活的任务处理框架4.1 共享工作队列4.2 自定义工作队列4.3 延迟工…...
ali 轻量服务器安装nginx
# Ubuntu sudo apt install nginx-light # 精简版 # CentOS sudo yum install nginx #启动并设置开机自启 sudo systemctl daemon-reload sudo systemctl start nginx sudo systemctl enable nginx #验证安装 nginx -v curl -I 127.0.0.1 #常用命令: # 重新加载配…...
2025年- H83-Lc191--139.单词拆分(动态规划)--Java版
1.题目描述 2.思路 字符串s是一个容器(一个背包),wordDict词典是物品,这里面的每个物品我们可以使用多次。 动归五部曲 (1)字符串的长度为i,dp[i]true。 dp[s.size] dp[0]代表空字符串 &#x…...
【好用但慎用】Windows 系统中将所有 WSL 发行版从 C 盘迁移到 非系统 盘的完整笔记(附 异常处理)
🚀 将所有 WSL 发行版从 C 盘迁移到 I 盘的完整教程(含 Podman / NVIDIA Workbench / Ubuntu 等) 【无标题】使用 Chocolatey 安装 WSL 管理工具 LxRunOffline-CSDN博客 免责声明 重要提示 在执行 WSL 迁移操作前,请务必仔细阅读…...
贪心算法思路详解
文章目录 一、贪心算法是什么?二、贪心算法原理三、再谈背包问题四、活动选择问题五、拟阵理论总结 一、贪心算法是什么? 贪心算法与动态规划算法一样是用于求解最优化类问题的算法,其本质上是基于动态规划算法的改进算法,其所求…...
Keil 安装 CMSIS-FreeRTOS 失败解决方案
一、问题现象 在 Keil 中安装 CMSIS-FreeRTOS 时出现以下错误: (1) 通过内置工具安装: (2)通过官网安装: 二、核心原因 Keil 版本过低,与 CMSIS-FreeRTOS 包不兼容: …...
Python打卡DAY33
DAY33:MLP神经网络的训练 恩师浙大疏锦行 知识点: PyTorch和cuda的安装查看显卡信息的命令行命令(cmd中使用)cuda的检查简单神经网络的流程 数据预处理(归一化、转换成张量)模型的定义 继承nn.Module类定义…...
RJ45 网口实现千兆传输速率(1Gbps)的原理,涉及物理层传输技术、线缆标准、信号调制及网络协议等多方面的协同设计。以下从技术维度展开详细解析:
一、千兆以太网的标准与物理层基础 1. 标准规范 千兆以太网遵循 IEEE 802.3ab(针对双绞线)和 IEEE 802.3z(针对光纤)标准,其中 RJ45 接口对应双绞线场景,核心是通过四对双绞线(CAT5e/CAT6 线缆…...
leetcode hot 100之:二叉树的层序遍历
层序遍历和前中后序遍历不一样,大家可以想象的是:前中后序遍历可以用递归,因为他是以子树为标准来选择的;那层序怎么办呢?怎么才能一层层地遍历呢? void First(TreeNode* root) {printf("%d",ro…...
深入解析BERT:语言分类任务的革命性引擎
“BERT的出现,如同在自然语言处理领域投下了一颗认知炸弹——它让机器真正学会了’联系上下文’。” ——自然语言处理研究者普遍共识 在自然语言处理(NLP)领域,2018年诞生的BERT(Bidirectional Encoder Representatio…...
Pycharm中Jupyter Notebook 插件常用快捷键
bg:Jupyter跟LINQPad很像,都是方便写的时候看数据用 快捷键功能Shift Enter执行当前单元格,并跳转到下一个单元格Ctrl Enter执行当前单元格,不跳转(留在当前单元格)Alt Enter执行当前单元格,…...
【Python】Excel表格操作:ISBN转条形码
一、效果 原始文件: 输出文件: 二、代码 import os import logging from openpyxl import load_workbook from openpyxl.drawing.image import Image as ExcelImage from barcode import EAN13 from barcode.writer import ImageWriterlogging.basicCo…...
大数据Hadoop集群搭建
文章目录 大数据Hadoop集群搭建一、VMware准备Linux虚拟机二、VMware虚拟机系统设置1、主机名、IP、SSH免密登录2、JDK环境部署3、防火墙、SELinux、时间同步 三、VMware虚拟机集群上部署HDFS集群1、集群规划2、上传&解压3、Hadoop安装包目录结构4、修改配置文件࿰…...
饼图:数据可视化的“切蛋糕”艺术
饼图,作为数据可视化家族中最经典、最易识别的成员之一,其核心功能如同其名——像切分蛋糕一样,直观展示一个整体(100%)被划分为若干组成部分的比例关系。 往期文章推荐: 20.用Mermaid代码画ER图:AI时代的…...
mysql server层做了什么
服务器处理客户端请求 服务器程序在处理来自客户端的查询请求时,大致需要分为3部分:连接管理、解析与优化、存储引擎。 连接管理 每当有一个客户端进程连接到服务器进程时,服务器进程都会创建一个线程专门处理与这个客户端的交互ÿ…...
3.5.1_1 信道划分介质访问控制(上)
在这个视频中我们要介绍信道划分、介质访问控制,这是两个词,我们先介绍一下什么叫做介质访问控制。 通过之前的学习,我们知道在计算机网络当中,有的信道它在逻辑上属于总线型,我们也可以把这种信道称为广播信道&#x…...
RPC常见问题回答
项目流程和架构设计 1.服务端的功能: 1.提供rpc调用对应的函数 2.完成服务注册 服务发现 上线/下线通知 3.提供主题的操作 (创建/删除/订阅/取消订阅) 消息的发布 2.服务的模块划分 1.网络通信模块 net 底层套用的moude库 2.应用层通信协议模块 1.序列化 反序列化数…...
数据分析和可视化:Py爬虫-XPath解析章节要点总结
重要知识点 XPath 概述:XPath 是一门可以在 XML 文件中查找信息的语言,也可用于 HTML 文件。它功能强大,提供简洁明了的路径表达式和多个函数,用于字符串、数值、时间比较等。1999 年成为 W3C 标准,常用于爬虫中抓取网…...
WIFI原因造成ESP8266不断重启的解决办法
一、报错 报错信息如下: 21:37:21.799 -> ets Jan 8 2013,rst cause:2, boot mode:(3,7) 21:37:21.799 -> 21:37:21.799 -> load 0x4010f000, len 3424, room 16 21:37:21.799 -> tail 0 21:37:21.799 -> chksum 0x2e 21:37:21.799 -> loa…...
OSI网络通信模型详解
OSI 模型就是把这整个过程拆解成了 7 个明确分工的步骤,每一层只负责自己那一摊事儿,这样整个系统才能顺畅运转,出了问题也容易找到“锅”在谁那。 核心比喻:寄快递 📦 想象你要把一份重要的礼物(你的数据…...
第五章 中央处理器
5.1 CPU的功能和基本构造 5.1.1 CPU的基本功能 5.1.2 CPU的基本结构 1.运算器 算术逻辑单元ALU 累加寄存器ACC 程序字状态寄存器PSW 计数器CT 暂存寄存器 通用寄存器组 移位器 通用寄存器供用户自由编程,可以存放数据和地址。而指令寄存器是专门用于存放指令的专用寄存器,…...
大模型学习入门——Day3:注意力机制
本系列笔记的教材:快乐学习大模型-DataWhale团队 注意力机制 注意力机制最先源于计算机视觉领域,其核心思想为当我们关注一张图片,我们往往无需看清楚全部内容而仅将注意力集中在重点部分即可。而在自然语言处理领域,我们往往也…...
C++ 学习笔记精要(二)
第一节 特殊类的设计 1. 一个类: 只能在堆上创建对象 关键点:自己控制析构 1.1 方法一: 使用delete禁掉默认析构函数 #include <iostream> using namespace std;class HeapOnly { public:HeapOnly(){_str new char[10];}~HeapOnly() delete;void Destroy(){delete[…...
博士,超28岁,出局!
近日,长沙市望城区《2025年事业引才博士公开引进公告》引发轩然大波——博士岗位年龄要求28周岁及以下,特别优秀者也仅放宽至30周岁。 图源:网络 这份规定让众多"高龄"博士生直呼不合理,并在社交平台掀起激烈讨论。 图源…...
macOS - 根据序列号查看机型、保障信息
文章目录 最近在看 MacBook 二手机,有个咸鱼卖家放个截图 说不清参数,于是想根据 序列号 查看机型。苹果提供了这样的网页: https://checkcoverage.apple.com/ (无需登录) 结果 2025-06-20(五)…...
C/C++ 高频八股文面试题1000题(一)
原作者:Linux教程,原文地址:C/C 高频八股文面试题1000题(一) 在准备技术岗位的求职过程中,C/C始终是绕不开的核心考察点。无论是互联网大厂的笔试面试,还是嵌入式、后台开发、系统编程等方向的岗位,C/C 都…...
C++ map 和 unordered_map 的区别和联系
C map 和 unordered_map 的区别和联系 map 和 unordered_map 都是 C 标准库中关联容器,用于存储键值对。它们的主要区别在于底层实现和性能特性,联系在于它们都提供了键值对的存储和访问功能。 区别: 特性mapunordered_map底层实现红黑树 …...
Sentinel实现原理
Sentinel 是阿里巴巴开源的分布式系统流量控制组件,主要用于服务保护,涵盖流量控制、熔断降级、系统负载保护等功能。 以下是 Sentinel 的实现原理,使用中文简要说明: 1. 总体架构 Sentinel 采用 轻量级 设计,分为 核…...
python打卡day37
疏锦行 知识点回顾: 1. 过拟合的判断:测试集和训练集同步打印指标 2. 模型的保存和加载 a. 仅保存权重 b. 保存权重和模型 c. 保存全部信息checkpoint,还包含训练状态 3. 早停策略 作业:对信贷数据集训练后保存权重…...
MySQL复杂查询优化实战:从多表关联到子查询的性能突破
文章目录 一、复杂查询性能瓶颈分析与优化框架二、多表关联查询的优化策略与实战1. JOIN顺序优化:基于成本估算的表关联策略2. 复合索引与JOIN条件优化3. 大表JOIN的分片处理 三、子查询优化:从嵌套到JOIN的转换艺术1. 标量子查询转换为JOIN2. EXISTS子查…...
LeetCode 680.验证回文串 II
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 思路详解: 代码: C代码: Java代码: 题目: 题目描述: 题目链接: 680. 验证…...
window显示驱动开发—输出合并器阶段
逻辑管道中的最后一步是通过模具或深度确定可见性,以及写入或混合输出以呈现目标,这可以是多种资源类型之一。 这些操作以及输出资源 (呈现目标) 绑定在输出合并阶段定义。 1. 核心功能与管线定位 输出合并是渲染管线的最终固定功能阶段,负…...
单片机开发日志cv MDK-ARM工具链迁移到MAKE
核心经验: STM32H7 多 RAM 区域,外设相关数据段必须放在 AXI SRAM(RAM)区,不能放在 DTCMRAM,否则外设无法访问,程序表面正常但外设全失效。迁移工程时,务必检查链接脚本的内存分布&a…...
大模型与搜索引擎的技术博弈及未来智能范式演进
基于认知革命与技术替代的全景综述 一、大模型对搜索引擎的替代性分析:技术范式与市场重构 (1)技术原理的代际分野 传统搜索引擎遵循 "爬虫抓取 - 索引构建 - 关键词排序" 的三段式架构,其核心是基于 PageRank 算法的…...
Ajax-入门
Ajax: 全称Asynchronous JavaScript And XML,异步的JavaScript和XML。其作用有如下2点: 与服务器进行数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。 异步交互:可以在不重新加载整个页面的情况下&a…...
FPGA基础 -- Verilog 共享任务(task)和函数(function)
Verilog 中共享任务(task)和函数(function) 的详细专业培训,适合具有一定 RTL 编程经验的工程师深入掌握。 一、任务(task)与函数(function)的基本区别 特性taskfunctio…...
c++set和pair的使用
set是C中的一种关联容器,具有以下特点: 存储唯一元素(不允许重复) 元素自动排序(默认升序) 基于红黑树实现(平衡二叉搜索树) 插入、删除和查找的时间复杂度为O(log n) 前言 在C…...
数据库中间件ShardingSphere5
一、高性能架构模式 数据库集群,第一种方式“读写分离”,第二种方式“数据库分片”。 1.1 读写分离架构 读写分离原理:将数据库读写操作分散到不同的节点上。 读写分离的基本实现: 主库负责处理事务性的增删改操作,…...
window显示驱动开发—使用状态刷新回调函数
用户模式显示驱动程序可以使用 Direct3D 运行时版本 10 State-Refresh回调函数 来实现无状态驱动程序或构建命令缓冲区前导数据。 Direct3D 运行时在调用 CreateDevice (D3D10 ) 函数时,向D3D10DDIARG_CREATEDEVICE结构的 pUMCallbacks 成员指向的D3D10DDI_CORELAY…...
windows11右击恢复为windows10
文章目录 前言一、问题描述二、解决方案 前言 为了解决win11的右击更多选项的问题 一、问题描述 win11的右键更多选项过于繁琐 二、解决方案 在windows11的终端管理员中输入如下代码: reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c…...
基于物联网的智能衣柜系统设计
标题:基于物联网的智能衣柜系统设计 内容:1.摘要 随着物联网技术的飞速发展,智能家居领域迎来了新的变革机遇。本研究的目的在于设计一种基于物联网的智能衣柜系统,以提升用户的衣物管理和使用体验。方法上,通过搭建物联网硬件平台ÿ…...
GM DC Monitor v2.0 卸载教程
以下俩种方法任选一种均可 第一种方法:一键自动卸载 进入到软件安装目录 卸载app 进入到app目录,运行一键卸载脚本:sh uninstall.sh 卸载es 进入到es目录,运行一键卸载脚本:sh uninstall.sh 卸载db 进入到db目录&a…...
C#上位机实现报警语音播报
我们在开发C#上位机时,有时候会需要将报警信息通过语音进行播报,今天跟大家分享一下具体的实现过程。 一、组件安装 首先我们创建好一个Windows窗体项目,然后添加System.Speech库引用。 点击引用,右击添加引用,在程…...
python自助棋牌室管理系统
目录 技术栈介绍具体实现截图系统设计研究方法:设计步骤设计流程核心代码部分展示研究方法详细视频演示试验方案论文大纲源码获取/详细视频演示 技术栈介绍 Django-SpringBoot-php-Node.js-flask 本课题的研究方法和研究步骤基本合理,难度适中…...
榕壹云婚恋相亲系统:ThinkPHP+UniApp打造高效婚配平台
引言 在数字化浪潮下,婚恋相亲行业正加速向线上迁移。榕壹云公司基于市场需求与技术积累,开发一款功能完备、技术开源的婚恋相亲小程序系统,为单身人士提供高效、安全的婚恋平台。本文将围绕系统背景、客户定位、核心技术、功能模块及优势场景展开详细解析,助力开发者与技…...
每日leetcode
2890. 重塑数据:融合 - 力扣(LeetCode) 题目 DataFrame report --------------------- | Column Name | Type | --------------------- | product | object | | quarter_1 | int | | quarter_2 | int | | quarter_3 | i…...
深入理解XGBoost(何龙 著)学习笔记(五)
深入理解XGBoost(何龙 著)学习笔记(五) 本文接上一篇,内容为线性回归,介绍三部分,首先介绍了"模型评估”,然后分别提供了线性回归的模型代码:scikit-learn的Linear…...
SelectDB 在 AWS Graviton ARM 架构下相比 x86 实现 36% 性价比提升
在海量数据分析中,追求高性价比已成为各大企业的主流趋势。ARM 架构凭借其高能效和低成本的特点,逐渐在数据中心崛起,成为理想的高性价比选择。基于 ARM 架构的 AWS Graviton 系列处理器,正是这一趋势的典型代表。Graviton 处理器…...
机器学习流量识别(pytorch+NSL-KDD+多分类建模)
本文主要实现以下功能,会提供完整的可运行的代码以及解释为什么这么设计。文章不会收费,若被限制查看,请私信我。 使用 NSL-KDD 数据集的CSV文件进行流量攻击检测,使用机器学习算法实现流量攻击检测,使用pytorch框架…...
三种经典算法无人机三维路径规划对比(SMA、HHO、GWO三种算法),Matlab代码实现
代码功能 该MATLAB代码用于对比三种元启发式优化算法(SMA、HHO、GWO三种算法, SMA黏菌算法、HHO哈里斯鹰优化算法、GWO灰狼优化算法) 在特定优化问题上的性能,运行环境MATLABR2020b或更高 : 初始化问题模型ÿ…...