linux中断和响应
1. 驱动中断函数基础
中断分为向量中断和非向量中断。向量中断为不同的中断分配不同的中断号,中断到来后自动跳转到地址执行,是硬件提供中断服务程序入口地址;非向量中断是多个中断程序共享一个入口地址,进入入口后再根据标志判断是那个中断。
1.1 request_irq
申请IRQ函数
/* * irq:中断号 * handler:回调函数,发生中断时,系统调用该函数 * flags:中断处理属性1.3 * devname:用于在/proc/interrupts标记,便于调试 * dev_id:私有指针,内核不直接使用,但在中断发生时会原样传递给处理函数。在共享中断中,此参数是唯一能区分不同设备的手段,一般传入本设备结构体或NULL
* 成功返回0 */ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id); /* * int irq:触发的中断号(同一个处理函数可以处理多个中断线时有用) * void *dev_id:设备标识符,即 request_irq 传入的 dev_id 参数 * 返回值 irqreturn_t: IRQ_NONE:中断不是本设备产生的(用于共享中断时判断) IRQ_HANDLED:中断已被本设备处理 IRQ_WAKE_THREAD:仅在线程化中断中使用,表示需要唤醒中断线程 */ typedef irqreturn_t (*irq_handler_t)(int, void*);
中断标志 flags 用于控制中断的行为,可组合使用(位或),常用标志包括:
| 标志 | 含义 |
|---|---|
IRQF_SHARED |
允许多个设备共享同一中断线。所有共享该中断的驱动必须设置此标志,并且处理函数必须能通过 dev_id 区分自己设备的中断。 |
IRQF_ONESHOT |
用于线程化中断。确保中断处理线程执行期间,该中断线被屏蔽,线程执行完后再自动重新使能。通常与线程化中断配合使用。 |
IRQF_TRIGGER_RISING / FALLING / HIGH / LOW |
指定中断触发电平(上升沿、下降沿、高电平、低电平)。这些标志通常由平台代码或设备树解析后自动设置,但驱动也可以显式指定。 |
IRQF_DISABLED |
(过时)旧内核用于要求在关中断上下文中执行处理函数。现代内核中,中断处理函数默认在关中断(本地 CPU 关中断)下执行,此标志已无意义。 |
IRQF_NO_SUSPEND |
系统休眠时不禁用该中断。 |
IRQF_FORCE_RESUME |
系统唤醒时强制恢复该中断。 |
IRQF_NO_THREAD |
禁止将该中断强制线程化(即使系统开启 force_irqthreads)。 |
IRQF_EARLY_RESUME |
在早期恢复阶段(syscore 阶段)恢复中断。 |
1.2 free_irq
释放IRQ函数
void free_irq(unsigned int irq, void* dev_id);
1.3 使能和屏蔽中断
释放IRQ函数
void disable_irq(int irq); // 等待目前的中断处理完成后返回,由于该函数等待指定中断处理完,如果在中断的顶半段调用该函数,会引起系统死锁 void disable_irq_nosync(int irq); // 立即返回 void enable_irq(int irq);
1.4 DECLARE_TASKLET
该函数是 Linux 内核中用于静态定义并初始化一个 tasklet 的宏。Tasklet 是一种常用的软中断机制,用于实现中断处理的下半部(bottom half),允许在稍后的安全时间执行延迟的工作,且保证在中断上下文中运行,不会睡眠
#include <linux/interrupt.h> /* * name:要定义的 tasklet 变量名 * func:tasklet 处理函数,类型为 void (*func)(unsigned long) * data:传递给 func 的参数,类型为 unsigned long,通常用于传递设备结构体指针或标志 */ DECLARE_TASKLET(name, func, data); DECLARE_TASKLET_DISABLED(name, func, data); // 初始为禁用状态 // 该宏展开后类似如下 struct tasklet_struct name = { .func = func, .data = data, .state = 0, // 对于 DECLARE_TASKLET // 其他成员由内核初始化 };
最后通过 tasklet_schedule(&xxx_tasklet) 运行
/* * 该模块模拟一个简单的设备驱动:使用内核定时器周期性地触发“中断”, * 中断上半部:调度一个 tasklet * 中断下半部:tasklet 执行实际工作(增加计数器并打印) */ #include <linux/module.h> #include <linux/interrupt.h> // tasklet 相关 #include <linux/timer.h> // 定时器模拟硬件中断 #include <linux/slab.h> // kzalloc #include <linux/spinlock.h> // 自旋锁 #define DRIVER_NAME "tasklet_second_dev" /* 设备结构体,包含所有相关资源 */ struct my_device { struct tasklet_struct tasklet; // tasklet 对象 struct timer_list timer; // 用于模拟硬件中断的定时器 spinlock_t lock; // 保护共享数据 counter unsigned long counter; // 统计 tasklet 执行次数 }; static struct my_device *my_dev; // 全局设备指针 /* tasklet 处理函数(下半部) */ static void my_tasklet_handler(unsigned long data) { struct my_device *dev = (struct my_device *)data; /* tasklet 运行在软中断上下文,不能睡眠,只能使用原子操作或自旋锁 */ spin_lock(&dev->lock); dev->counter++; spin_unlock(&dev->lock); pr_info("Tasklet executed, counter = %lu\n", dev->counter); /* 此处不能调用可能睡眠的函数(如 kmalloc(GFP_KERNEL)) */ } /* 中断上半部处理程序(由定时器回调调用) */ static void simulated_interrupt(struct timer_list *t) { struct my_device *dev = container_of(t, struct my_device, timer); pr_info("Simulated interrupt, scheduling tasklet\n"); /* 调度 tasklet:将 tasklet 挂入队列,稍后在软中断中执行 */ tasklet_schedule(&dev->tasklet); /* 硬件中断处理还需要: * 1. 读取中断状态寄存器,确认是否是自己的设备产生的中断 * 2. 清除硬件中断标志 * 3. 如果是共享中断,返回 IRQ_NONE 表示不是自己的中断 */ /* 重新启动定时器,实现周期性中断(如每秒一次) */ mod_timer(&dev->timer, jiffies + HZ); } /* 模块初始化 */ static int __init tasklet_second_init(void) { int ret; /* 1. 分配设备结构体内存 */ my_dev = kzalloc(sizeof(struct my_device), GFP_KERNEL); if (!my_dev) return -ENOMEM; /* 2. 初始化自旋锁 */ spin_lock_init(&my_dev->lock); /* 3. 初始化 tasklet:绑定处理函数,传入设备指针作为参数 */ tasklet_init(&my_dev->tasklet, my_tasklet_handler, (unsigned long)my_dev); /* 4. 初始化定时器,用于模拟硬件中断 */ timer_setup(&my_dev->timer, simulated_interrupt, 0); mod_timer(&my_dev->timer, jiffies + HZ); // 1 秒后触发第一次 pr_info("Tasklet sample module loaded, timer started.\n"); return 0; } /* 模块退出 */ static void __exit tasklet_second_exit(void) { if (!my_dev) return; /* 1. 删除定时器,确保不会再调用模拟中断 */ del_timer_sync(&my_dev->timer); /* 2. 杀死 tasklet:确保 tasklet 不会再次被调度,并等待正在运行的实例完成 */ tasklet_kill(&my_dev->tasklet); /* 此时可以安全地释放资源,因为不会有任何 tasklet 在运行 */ pr_info("Tasklet killed, final counter = %lu\n", my_dev->counter); /* 3. 释放设备结构体 */ kfree(my_dev); pr_info("Tasklet sample module unloaded\n"); } module_init(tasklet_second_init); module_exit(tasklet_second_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("cear");
1.5 INIT_WORK
该函数是 Linux 内核中用于动态初始化一个工作项(work_struct)的宏。工作项是工作队列机制的核心,允许将任务延迟到进程上下文中执行,从而可以睡眠、持有互斥锁等,适用于处理较复杂或可能阻塞的操作(如大量的数据拷贝、文件系统操作等)
#include <linux/workqueue.h> /* * work:指向要初始化的 struct work_struct 变量的指针 * func:工作处理函数,将在工作项被调度时执行 */ INIT_WORK(struct work_struct *work, work_func_t func); // 宏展开后类似如下 struct work_struct { atomic_long_t data; // 内部状态和标志 struct list_head entry; // 用于挂入队列的链表节点 work_func_t func; // 工作处理函数 };
最后通过 schedule_work(&my_work)调度工作队列执行
/* * - 动态初始化工作项(INIT_WORK) * - 使用系统默认工作队列调度工作(schedule_work) * - 在模拟中断(定时器回调)中调度工作 * - 工作函数在进程上下文中执行,可以睡眠 * - 模块卸载时确保工作项被取消(cancel_work_sync) */ #include <linux/module.h> #include <linux/workqueue.h> // work_struct 相关 #include <linux/timer.h> // 定时器模拟中断 #include <linux/slab.h> // kmalloc #include <linux/spinlock.h> // 自旋锁 #include <linux/delay.h> // msleep (仅用于演示可睡眠) #define DRIVER_NAME "workqueue_sample" /* 设备结构体,包含工作项、定时器、计数器及锁 */ struct my_device { struct work_struct work; // 工作项 struct timer_list timer; // 模拟中断的定时器 spinlock_t lock; // 保护 counter unsigned long counter; // 统计工作项执行次数 bool stop; // 停止标志,用于退出循环(可选) }; static struct my_device *my_dev; // 全局设备指针 /* 中断下半部:工作队列处理函数(运行在进程上下文,可以睡眠) */ static void my_work_handler(struct work_struct *work) { struct my_device *dev = container_of(work, struct my_device, work); unsigned long flags; /* 可以进行需要睡眠的操作(例如长时间计算、I2C 读写等) */ msleep(10); /* 更新计数器(需要保护,因为可能与模块卸载路径并发) */ spin_lock_irqsave(&dev->lock, flags); dev->counter++; spin_unlock_irqrestore(&dev->lock, flags); pr_info("Work handler executed, counter = %lu\n", dev->counter); } /* 中断上半部:定时器回调(软中断上下文,不能睡眠) */ static void simulated_interrupt(struct timer_list *t) { struct my_device *dev = from_timer(dev, t, timer); pr_info("Simulated interrupt, scheduling work\n"); /* 调度工作项到系统默认工作队列 */ schedule_work(&dev->work); /* 重新启动定时器,模拟周期性中断(每2秒一次) */ mod_timer(&dev->timer, jiffies + msecs_to_jiffies(2000)); } /* 模块初始化 */ static int __init workqueue_sample_init(void) { int ret; /* 1. 分配设备结构体内存 */ my_dev = kzalloc(sizeof(struct my_device), GFP_KERNEL); if (!my_dev) return -ENOMEM; /* 2. 初始化自旋锁 */ spin_lock_init(&my_dev->lock); /* 3. 初始化工作项:绑定处理函数,注意处理函数参数是 work_struct 指针 */ INIT_WORK(&my_dev->work, my_work_handler); /* 4. 初始化定时器,用于模拟中断 */ timer_setup(&my_dev->timer, simulated_interrupt, 0); mod_timer(&my_dev->timer, jiffies + msecs_to_jiffies(2000)); // 2秒后首次触发 pr_info("Workqueue sample module loaded, timer started (period 2s)\n"); return 0; } /* 模块退出 */ static void __exit workqueue_sample_exit(void) { if (!my_dev) return; pr_info("Unloading module, stopping timer and cancel work...\n"); /* 1. 删除定时器,确保不再触发新的工作调度 */ del_timer_sync(&my_dev->timer); /* 2. 取消可能正在执行或待处理的工作项 * cancel_work_sync 会等待正在工作项完成 * 并确保之后不会再有该工作项被调度 * 返回 true 表示工作项在取消前尚未开始执行 * false 表示工作项已经开始执行或已经完成 */ if (cancel_work_sync(&my_dev->work)) pr_info("Work was pending and has been cancelled.\n"); else pr_info("Work was not pending or already executed.\n"); /* 此时可以安全地访问 counter(无并发工作项) */ pr_info("Final counter = %lu\n", my_dev->counter); /* 3. 释放设备结构体 */ kfree(my_dev); pr_info("Workqueue sample module unloaded\n"); } module_init(workqueue_sample_init); module_exit(workqueue_sample_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("cear");

浙公网安备 33010602011771号