[TOC]
问题描述:
在 Linux 驱动中,中断处理的上半部(硬中断)和下半部分离是核心机制,核心目的是上半部快速响应中断(禁止中断嵌套),下半部延迟处理耗时任务(可调度、允许中断)。常用实现方式有 3 种:tasklet、工作队列(workqueue)、软中断,其中 tasklet 和工作队列最常用日志
分析步骤
第1步:
第2步:
...代码片段
- tasklet 方式(软中断上下文,不可睡眠) tasklet 基于软中断实现,特点是同一 tasklet 不会并行执行,不同 tasklet 可在不同 CPU 并行,适合短任务。
/*
* @Author: your name
* @Date: 2025-09-01 19:31:14
* @LastEditTime: 2025-09-01 19:35:48
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\tasklet.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
// 1. 定义Tasklet和共享数据
static struct tasklet_struct my_tasklet;
static int tasklet_count = 0; // 计数:下半部处理次数
static struct timer_list irq_timer; // 模拟中断的定时器
// 2. 下半部:Tasklet处理函数
static void tasklet_handler(unsigned long data) {
// 模拟耗时处理,不可调用sleep类阻塞函数
tasklet_count++;
printk(KERN_INFO "[下半部-Tasklet] 处理完成!总次数: %d (CPU: %d)\n",
tasklet_count, smp_processor_id());
}
// 3. 上半部:定时器回调(模拟中断,触发Tasklet)
static void timer_irq_handler(struct timer_list *t) {
printk(KERN_INFO "[上半部] 收到中断,触发Tasklet...\n");
// 触发Tasklet(上半部核心工作)
tasklet_schedule(&my_tasklet);
// 重新启动定时器
mod_timer(t, jiffies + msecs_to_jiffies(1000));
}
// 4. 驱动初始化
static int __init tasklet_demo_init(void) {
// 初始化Tasklet
tasklet_init(&my_tasklet, tasklet_handler, 0);
// 初始化定时器(1秒触发一次)
timer_setup(&irq_timer, timer_irq_handler, 0);
mod_timer(&irq_timer, jiffies + msecs_to_jiffies(1000));
printk(KERN_INFO "Tasklet demo初始化完成\n");
return 0;
}
// 5. 驱动卸载
static void __exit tasklet_demo_exit(void) {
del_timer(&irq_timer);
tasklet_kill(&my_tasklet); // 确保Tasklet已执行完毕
printk(KERN_INFO "Tasklet demo卸载完成,总处理次数: %d\n", tasklet_count);
}
module_init(tasklet_demo_init);
module_exit(tasklet_demo_exit);
MODULE_LICENSE("GPL");
- 工作队列方式(内核线程上下文,可睡眠) 工作队列将任务交给内核线程(如kworker)执行,支持睡眠(可调用kmalloc、msleep等阻塞函数),适合长耗时任务。
关键结论
工作队列的任务不在调用者的线程中执行,而是由内核专门创建的 kworker 线程执行。
这种设计的优势是:任务执行与调用者线程解耦,调用者无需等待任务完成,实现了真正的异步处理;同时,内核线程运行在进程上下文,允许睡眠(如 msleep()),适合处理耗时操作。
因此,工作队列本质上是 “基于内核线程的异步任务调度机制”,通过独立的内核线程实现任务的异步执行。
//普通的工作队列
/*
* @Author: your name
* @Date: 2025-09-01 19:45:06
* @LastEditTime: 2025-09-01 19:53:44
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\workqueue.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/delay.h>
/*
工作队列使用步骤:
1. 定义工作队列和工作队列处理函数
INIT_WORK
2. 触发工作队列,使其能够调用回调函数
schedule_work
*/
// 1. 定义工作队列和共享数据
static struct work_struct my_work;
static int work_count = 0; // 计数:下半部处理次数
static struct timer_list irq_timer; // 模拟中断的定时器
// 2. 下半部:工作队列处理函数(进程上下文,可睡眠)
static void work_handler(struct work_struct *work) {
// 模拟耗时处理(可睡眠)
msleep(500); // 睡眠500ms(Tasklet/软中断中禁止此操作)
work_count++;
printk(KERN_INFO "[下半部-工作队列] 处理完成!总次数: %d (CPU: %d)\n",
work_count, smp_processor_id());
}
// 3. 上半部:定时器回调(模拟中断,触发工作队列)
static void timer_irq_handler(struct timer_list *t) {
printk(KERN_INFO "[上半部] 收到中断,触发工作队列...\n");
// 调度工作队列(上半部核心工作)
schedule_work(&my_work);
// 重新启动定时器
mod_timer(t, jiffies + msecs_to_jiffies(1000));
}
// 4. 驱动初始化
static int __init workqueue_demo_init(void) {
// 初始化工作队列
INIT_WORK(&my_work, work_handler);
// 初始化定时器(1秒触发一次)
timer_setup(&irq_timer, timer_irq_handler, 0);
mod_timer(&irq_timer, jiffies + msecs_to_jiffies(1000));
printk(KERN_INFO "工作队列demo初始化完成\n");
return 0;
}
// 5. 驱动卸载
static void __exit workqueue_demo_exit(void) {
del_timer(&irq_timer);
flush_work(&my_work); // 等待工作队列执行完毕
printk(KERN_INFO "工作队列demo卸载完成,总处理次数: %d\n", work_count);
}
module_init(workqueue_demo_init);
module_exit(workqueue_demo_exit);
MODULE_LICENSE("GPL");
//常用延时工作队列
/*
* @Author: your name
* @Date: 2025-09-02 11:01:32
* @LastEditTime: 2025-09-02 11:10:51
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\delay_work.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
/*
schedule_delayed_work与普通延时队列的区别:
schedule_delayed_work() 是内核推荐的延迟任务实现方式,通过封装 delayed_work。
与手动实现的 “延时队列” 相比,它减少了代码量和出错风险,适合大多数需要延迟执行异步任务的驱动场景(如设备超时处理、周期性状态刷新等)
schedule_delayed_work 使用步骤如下:
1. 定义延时工作项
2. 初始化延时工作项
3. 调度延时工作项
4. 取消延时工作项
5. 驱动初始化:创建自定义工作队列并提交任务
6. 驱动卸载:清理工作队列
*/
// 定义延时工作项
static struct delayed_work my_delayed_work;
// 任务计数器
static int work_count = 0;
// 延迟时间(500ms)
#define DELAY_MS 500
// 工作项处理函数(进程上下文,可睡眠)
static void work_handler(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
work_count++;
printk(KERN_INFO "[%d] 延迟工作执行,CPU:%d\n",
work_count, smp_processor_id());
// 模拟耗时操作
msleep(1000);
// 重新调度,形成周期性执行(间隔 DELAY_MS)
schedule_delayed_work(dwork, msecs_to_jiffies(DELAY_MS));
}
static int __init delayed_work_init(void)
{
printk(KERN_INFO "延迟工作示例初始化...\n");
// 初始化延时工作项
INIT_DELAYED_WORK(&my_delayed_work, work_handler);
// 首次调度:延迟 DELAY_MS 后执行
schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(DELAY_MS));
printk(KERN_INFO "首次工作将在 %d ms 后执行\n", DELAY_MS);
return 0;
}
static void __exit delayed_work_exit(void)
{
// 取消未执行的工作项
cancel_delayed_work_sync(&my_delayed_work);
printk(KERN_INFO "驱动卸载,共执行 %d 次工作\n", work_count);
}
module_init(delayed_work_init);
module_exit(delayed_work_exit);
MODULE_LICENSE("GPL");
//标准延时队列用法
/*
* @Author: your name
* @Date: 2025-09-02 10:41:49
* @LastEditTime: 2025-09-02 10:55:43
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\delayed_work.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/delay.h> // 内核中msleep的头文件
/*
延时工作队列步骤:
1. 定义延时工作队列和工作项
2. 定义工作项的处理函数
3. 初始化工作项
4. 创建自定义工作队列
5. 提交工作项到自定义工作队列
6. 驱动初始化:创建自定义工作队列并提交任务
7. 驱动卸载:清理工作队列
*/
// 定义延时工作项(包含工作队列和内置定时器)
static struct delayed_work my_delayed_work;
// 任务计数器
static int task_counter = 0;
// 延迟时间(毫秒,可修改)
#define DELAY_MS 2000 // 每次任务延迟2秒执行
// 延时队列的任务处理函数(进程上下文,可睡眠)
static void delayed_work_handler(struct work_struct *work)
{
// 转换为delayed_work结构体(通过container_of获取完整结构)
struct delayed_work *dwork = to_delayed_work(work);
task_counter++;
printk(KERN_INFO "[延时队列] 第%d次执行任务当前CPU:%d\n",
task_counter, smp_processor_id());
// 模拟耗时操作(如设备数据处理、状态上报等)
msleep(500); // 进程上下文允许睡眠
// 重新调度延时任务(形成循环执行)
queue_delayed_work(system_wq, dwork, msecs_to_jiffies(DELAY_MS));
}
// 驱动初始化函数
static int __init delayed_queue_init(void)
{
printk(KERN_INFO "延时队列驱动初始化...\n");
// 初始化延时工作项,绑定处理函数
INIT_DELAYED_WORK(&my_delayed_work, delayed_work_handler);
// 首次调度延时任务(延迟DELAY_MS后执行)
if (!queue_delayed_work(system_wq, &my_delayed_work, msecs_to_jiffies(DELAY_MS))) {
printk(KERN_ERR "首次调度延时任务失败!\n");
return -EFAULT;
}
printk(KERN_INFO "延时队列启动成功,将在%d毫秒后执行首次任务\n", DELAY_MS);
return 0;
}
// 驱动卸载函数
static void __exit delayed_queue_exit(void)
{
// 取消所有未执行的延时任务,等待当前任务完成
cancel_delayed_work_sync(&my_delayed_work);
printk(KERN_INFO "延时队列驱动卸载,共执行%d次任务\n", task_counter);
}
// 注册驱动入口和出口
module_init(delayed_queue_init);
module_exit(delayed_queue_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linux Delayed Workqueue Demo");
MODULE_AUTHOR("Your Name");
//自定义工作队列
/*
* @Author: your name
* @Date: 2025-09-02 10:50:56
* @LastEditTime: 2025-09-02 10:57:14
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\custom_workqueue.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/delay.h>
/*
自定义工作队列步骤:
1. 定义自定义工作队列和工作项
2. 定义工作项的处理函数
3. 初始化工作项
4. 创建自定义工作队列
5. 提交工作项到自定义工作队列
6. 驱动初始化:创建自定义工作队列并提交任务
7. 驱动卸载:清理工作队列
*/
// 1. 定义自定义工作队列和工作项
static struct workqueue_struct *my_wq; // 自定义工作队列
static struct work_struct work1, work2; // 两个工作项(任务)
static int work_count = 0; // 任务执行计数器
// 2. 工作项1的处理函数(进程上下文,可睡眠)
static void work_func1(struct work_struct *work)
{
work_count++;
printk(KERN_INFO "[工作项1] 执行任务(计数:%d运行在CPU:%d\n",
work_count, smp_processor_id());
// 模拟耗时操作(如数据处理)
msleep(100); // 进程上下文允许睡眠
}
// 3. 工作项2的处理函数
static void work_func2(struct work_struct *work)
{
work_count++;
printk(KERN_INFO "[工作项2] 执行任务(计数:%d),运行在CPU:%d\n",
work_count, smp_processor_id());
// 模拟另一种耗时操作(如设备控制)
msleep(200);
}
// 4. 驱动初始化:创建自定义工作队列并提交任务
static int __init custom_workqueue_init(void)
{
printk(KERN_INFO "自定义工作队列初始化...\n");
// 创建自定义工作队列(名称为"my_custom_wq",单线程模式),也可使用系统默认队列 system_wq)
// 参数说明:
// - 第二个参数 0 表示单线程(每个CPU一个线程可设为 WQ_UNBOUND)
/* 当执行ps命令可以看到创建的工作队列的名称
838 root [my_custom_wq]
*/
my_wq = create_workqueue("my_custom_wq");
if (!my_wq) {
printk(KERN_ERR "创建自定义工作队列失败!\n");
return -ENOMEM;
}
// 初始化工作项,绑定处理函数
INIT_WORK(&work1, work_func1);
INIT_WORK(&work2, work_func2);
// 将工作项提交到自定义工作队列(异步执行)
queue_work(my_wq, &work1);
queue_work(my_wq, &work2);
printk(KERN_INFO "工作项已提交到自定义工作队列,等待执行...\n");
return 0;
}
// 5. 驱动卸载:清理工作队列
static void __exit custom_workqueue_exit(void)
{
// 等待队列中所有任务执行完毕,然后销毁队列
flush_workqueue(my_wq); // 确保所有工作项完成
destroy_workqueue(my_wq); // 销毁自定义工作队列
printk(KERN_INFO "自定义工作队列已卸载,总执行任务数:%d\n", work_count);
}
// 注册驱动入口和出口
module_init(custom_workqueue_init);
module_exit(custom_workqueue_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Custom Workqueue Demo");
MODULE_AUTHOR("Your Name");
3.软中断(Softirq):多核安全,并行处理
特点:
静态分配(编译时注册),可在多核 CPU 上并行执行(同类型软中断可在不同核同时运行);
适合高频、高性能场景(如网络数据包处理);
处理函数运行在中断上下文,不能睡眠。/*
* @Author: your name
* @Date: 2025-09-02 11:44:06
* @LastEditTime: 2025-09-02 11:46:58
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\softirq.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
// 1. 定义共享数据和软中断编号(使用空闲编号,如31)
#define SOFTIRQ_NUM 31
static int softirq_count = 0; // 计数:下半部处理次数
static struct timer_list irq_timer; // 模拟中断的定时器
// 2. 下半部:软中断处理函数(执行耗时操作)
static void softirq_handler(struct softirq_action *action) {
// 模拟耗时处理(打印信息+计数)
softirq_count++;
printk(KERN_INFO "[下半部-软中断] 处理完成!总次数: %d (CPU: %d)\n",
softirq_count, smp_processor_id());
}
// 3. 上半部:定时器回调(模拟中断服务程序,快速触发下半部)
static void timer_irq_handler(struct timer_list *t) {
printk(KERN_INFO "[上半部] 收到中断,触发软中断...\n");
// 触发软中断(上半部核心工作:快速调度下半部)
raise_softirq(SOFTIRQ_NUM);
// 重新启动定时器(模拟周期性中断)
mod_timer(t, jiffies + msecs_to_jiffies(1000));
}
// 4. 驱动初始化
static int __init softirq_demo_init(void) {
// 注册软中断
open_softirq(SOFTIRQ_NUM, softirq_handler);
// 初始化定时器(模拟硬件中断,1秒触发一次)
timer_setup(&irq_timer, timer_irq_handler, 0);
mod_timer(&irq_timer, jiffies + msecs_to_jiffies(1000));
printk(KERN_INFO "软中断demo初始化完成\n");
return 0;
}
// 5. 驱动卸载
static void __exit softirq_demo_exit(void) {
// 注销定时器和软中断
del_timer(&irq_timer);
printk(KERN_INFO "软中断demo卸载完成,总处理次数: %d\n", softirq_count);
}
module_init(softirq_demo_init);
module_exit(softirq_demo_exit);
MODULE_LICENSE("GPL");
图片
结论
待查资料问题
- 问题 1:?
- 问题 2:?
参考链接
- 官方文档
