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 驱动程序至关重要。在实际开发中,开发者需要根据具体的应用场景,合理选择和运用这些中断处理机制,以实现最佳的系统性能和功能。