当前位置: 首页 > news >正文

【系列文章】Linux系统中断的应用04-共享工作队列实验

【系列文章】Linux系统中断的应用04-共享工作队列实验

该文章为系列文章:Linux系统中断的应用中的第04篇
该系列的导航页连接:
【系列文章】Linux系统中断的应用-导航页


文章目录

  • 【系列文章】Linux系统中断的应用04-共享工作队列实验
    • 一、什么是工作队列
    • 二、共享工作队列
      • 2.1工作队列相关接口函数
        • 初始化函数
        • 调度/取消调度工作队列函数
      • 2.2实验程序的编写
      • 2.3运行测试
    • 三、自定义工作队列
      • 3.1工作队列相关结构体
      • 3.2工作队列相关接口函数
      • 3.3实验程序的编写
        • 驱动程序编写
        • 运行测试


在上一篇文章我们学习了中断下文的一种实验方式——软中断,本章节我们来学习中断下文的另一种实现方式——工作队列。工作队列是操作系统中管理和调度异步任务执行的一种机制,接下来开始学习工作队列吧。

一、什么是工作队列

工作队列是实现中断下半部分的机制之一,是一种用于管理任务的数据结构或机制。它通常用于多线程,多进程或分布式系统中,用于协调和分配待处理的任务给可用的工作线程或工作进程。

工作队列的基本原理是将需要执行的任务按顺序排列在队列中,并提供一组工作线程或者工作进程来处理队列中的任务。当有新的任务到达时,它们会被添加到队列的末尾,工作线程或工作进程从队列的头部获取任务,并执行相应的处理操作。

工作队列和之前学习的 tasklet 有什么不同呢?tasklet 也是实现中断下半部分的机制之一。他们最主要的区别是 tasklet 不能休眠,而工作队列是可以休眠的,所以 tasklet 可以用来处理比较耗时间的事情,而工作队列可以处理更耗时间的事情。

工作队列将工作推后以后,会交给内核线程去执行。Linux 在启动过程中会创建一个工作者内核线程,这个线程创建以后处于 sleep 状态。当有工作需要处理的时候,会唤醒这个线程去处理工作。

在内核中,工作队列包括共享工作队列和自定义工作队列这俩种类型。这两种类型的工作队列具有不同的特点和用途。

1 共享队列是由内核管理的全局工作队列,用于处理内核中一些系统级任务。共享工作队列
是内核中一个默认工作队列,可以由多个内核组件和驱动程序共享使用。
2 自定义工作队列是由内核或驱动程序创建的特定工作队列,用于处理特定的任务。自定义工
作队列通常与特定的内核模块或驱动程序相关联,用于执行该模块或驱动程序相关的任务

二、共享工作队列

在 Linux 内核中,使用 work_struct 结构体表示一个工作项,这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,如下图所示,流水线相当于工作队列,流水线上一个个等待处理的物料相当于一个个工作。机器相当于内核线程或进程。
在这里插入图片描述
work_struct 结构体表示一个工作项,定义在 include/linux/workqueue.h 中,如下所示:

struct work_struct {atomic_long_t data;struct list_head entry;work_func_t func; /* 工作队列处理函数 */
};
typedef void (*work_func_t)(struct work_struct *work); //工作函数

2.1工作队列相关接口函数

初始化函数

在实际的驱动开发中,我们只需要定义工作项(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作,INIT_WORK 宏定义如下:

#define INIT_WORK(_work,_func)

INIT_WORK 宏接受两个参数:_work 和 _func,分别表示要初始化的工作项和工作项的处理函数。

也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f)

参数 n 表示定义的工作(work_struct),f 表示工作对应的处理函数。

调度/取消调度工作队列函数

和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:

static inline bool schedule_work(struct work_struct *work)

参数是指向工作项的指针。这个函数作用是将工作项提交到工作队列中,并请求调度器在合适的时机执行工作项。该函数会返回一个布尔值,表示工作项是否成功被提交到工作队列。

如果想要取消该工作项的调度,使用以下函数:

bool cancel_work_sync(struct work_struct *work);

参数是指向工作项的指针。这个函数的作用是取消该工作项的调度。如果工作项已经在工作队列中,它将被从队列中移除。如果工作项已经在工作队列中,它将被从队列中移除,并等待工作项执行完成。函数返回一个布尔值,表示工作项是否成功取消。

2.2实验程序的编写

本实验将实现注册显示屏触摸中断,每按当触摸 LCD 显示屏就会触发中断服务函数,在中断服务函数中提交工作项到共享工作队列中,打印“This id test_interrupt”和“This istest_work”。

在驱动程序中的模块初始化函数中,我们将 GPIO 转换为中断号,并使用 request_irq 函数请求中断,然后初始化工作项。当中断被触发时,中断处理函数被调用,并将工作项提交到共享工作队列中,最终由工作项处理函数异步执行。编写完成的 interrupt.c 代码如下所示。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>int irq;
struct work_struct test_workqueue;// 工作项处理函数
void test_work(struct work_struct *work)
{msleep(1000);printk("This is test_work\n");
}// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{printk("This is test_interrupt\n");// 提交工作项到工作队列schedule_work(&test_workqueue);return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret;irq = gpio_to_irq(101); // 将 GPIO 映射为中断号printk("irq is %d\n", irq);// 请求中断ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);if (ret < 0){printk("request_irq is error\n");return -1;}// 初始化工作项INIT_WORK(&test_workqueue, test_work);return 0;
}static void interrupt_irq_exit(void)
{free_irq(irq, NULL); // 释放中断printk("bye bye\n");
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");

2.3运行测试

在这里插入图片描述
在上图中,可以看到打印中断处理函数中添加的打印“This is test_interrupt”被多次打印,说明触发了好几次中断上文,那么中断上文会多次调度中断下文,所以也会打印工作项处理函数中添加的打印“This is test_work”。但是为什么只会打印俩次“This is test_work”呢?这是因为在中断上文调度工作项处理函数之后,内核没有来得及去执行工作项处理函数,没有执行相当于无效操作,有效的执行则打印了俩次“This is test_work”。

三、自定义工作队列

3.1工作队列相关结构体

在 Linux 内核中,结构体 struct work_struct 描述的是要延迟执行的工作项,定义在include/linux/workqueue.h 当中,如下所示

struct work_struct {atomic_long_t data; // 工作项的数据字段struct list_head entry; // 工作项在工作队列中的链表节点work_func_t func; // 工作项的处理函数#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map; // 锁依赖性映射#endif
};

这些工作组织成工作队列,内核使用 struct workqueue_struct 结构体描述一个工作队列,定义在 include/linux/workqueue.h 当中,如下所示:

struct workqueue_struct {struct list_head pwqs; // 工作队列上的挂起工作项列表struct list_head delayed_works; // 延迟执行的工作项列表struct delayed_work_timer dwork_timer; // 延迟工作项的定时器struct workqueue_attrs *unbound_attrs; // 无绑定工作队列的属性struct pool_workqueue *dfl_pwq; // 默认的池化工作队列... 
};

3.2工作队列相关接口函数

在 Linux 内核中,create_workqueue 函数用于创建一个工作队列,函数原型如下所示:

struct workqueue_struct *create_workqueue(const char *name);

参数 name 是创建的工作队列的名字。使用这个函数可以给每个 CPU 都创建一个 CPU 相
关的工作队列。创建成功返回一个 struct workqueue_struct 类型指针,创建失败返回 NULL。

如果只给一个 CPU 创建一个 CPU 相关的工作队列,使用以下函数。

#define create_singlethread_workqueue(name) \ 
alloc_workqueue("%s", WQ_SINGLE_THREAD, 1, name)

参数 name 是创建的工作队列的名字。使用这个函数只会给一个 CPU 创建一个 CPU 相关的工作队列。创建成功之后返回一个 struct workqueue_struct 类型指针,创建失败返回 NULL。

当工作队列创建好之后,需要将要延迟执行的工作项放在工作队列上,调度工作队列,使用 queue_work_on 函数,函数原型如下所示:

bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work);

该函数有三个参数,第一个参数是一个整数 cpu,第二个参数是一个指向 struct workqueue_struct 的指针 wq,第三个参数是一个指向 struct work_struct 的指针 work。该函数的返回类型是布尔值,表示是否成功调度工作队列。 queue_work_on 函数还有其他变种,比如 queue_work 函数,这里略过,其实思路是一致的,用于将定义好的工作项立即添加到工作队列中,并在工作队列可用时立即执行。

如果要取消一个已经调度的工作,使用函数 bool cancel_work_sync,函数原型如下所示:

bool cancel_work_sync(struct work_struct *work);

函数的作用是取消一个已经调度的工作,如果被取消的工作已经正在执行,则会等待他执
行完成再返回。

在 Linux 内核中,使用 flush_workqueue 函数将刷新该工作队列中所有已提交但未执行的工作项。函数原型如下所示:

void flush_workqueue(struct workqueue_struct *wq);

该函数参数是一个指向 struct workqueue_struct 类型的指针 wq。函数的作用是刷新工作队列,告诉内核尽快处理工作队列上的工作。

如果要删除自定义的工作队列,使用 destroy_workqueue 函数,函数原型如下所示:

void destroy_workqueue(struct workqueue_struct *wq);

该函数参数是一个指向 struct workqueue_struct 类型的指针 wq。

3.3实验程序的编写

驱动程序编写

本实验将实现注册显示屏触摸中断,每按当触摸 LCD 显示屏就会触发中断服务函数,在中断服务函数中提交工作项到工作队列中,打印“This id test_interrupt”和“This istest_work”。

在驱动程序中的模块初始化函数中,我们将 GPIO 转换为中断号,并使用 request_irq 函数请求中断,然后创建自定义工作队列,初始化工作项。当中断被触发时,中断处理函数被调用,并将工作项提交到自定义工作队列中,最终由工作项处理函数异步执行。编写完成的 interrupt.c代码如下所示

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>int irq;
struct workqueue_struct *test_workqueue;
struct work_struct test_workqueue_work;// 工作项处理函数
void test_work(struct work_struct *work)
{msleep(1000);printk("This is test_work\n");
}// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{printk("This is test_interrupt\n");// 提交工作项到自定义工作队列queue_work(test_workqueue, &test_workqueue_work);return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret;irq = gpio_to_irq(101); // 将 GPIO 映射为中断号printk("irq is %d\n", irq);// 请求中断ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);if (ret < 0){printk("request_irq is error\n");return -1;}test_workqueue = create_workqueue("test_workqueue"); // 创建工作队列INIT_WORK(&test_workqueue_work, test_work); // 初始化工作项return 0;
}static void interrupt_irq_exit(void)
{free_irq(irq, NULL); // 释放中断cancel_work_sync(&test_workqueue_work); // 取消工作项flush_workqueue(test_workqueue); // 刷新工作队列destroy_workqueue(test_workqueue); // 销毁工作队列printk("bye bye\n");
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
运行测试

在这里插入图片描述
在上图中,可以看到打印中断处理函数中添加的打印“This is test_interrupt”被打印了俩次,说明触发了 2 次中断上文,那么中断上文会调度 2 次中断下文,所以也会打印 2 次工作项处理函数中添加的打印“This is test_work”。

在按屏幕之后,立即输入 ps -aux|grep test_workqueue 命令可以查看自己创建的工作队列,如下所示:
在这里插入图片描述


文章转载自:

http://LSYNWRS5.dncgb.cn
http://GM8rUkaP.dncgb.cn
http://oqob122a.dncgb.cn
http://I5ja0Vwk.dncgb.cn
http://oDdcWMiO.dncgb.cn
http://AaQP0VrS.dncgb.cn
http://zP8C1hE6.dncgb.cn
http://8GWAmehT.dncgb.cn
http://iC4ryHCd.dncgb.cn
http://afuvJ8RV.dncgb.cn
http://8QtYH5P1.dncgb.cn
http://Rq2GFkpk.dncgb.cn
http://AUh9odue.dncgb.cn
http://msYS3NcX.dncgb.cn
http://YDRReDTy.dncgb.cn
http://zShs6Mrw.dncgb.cn
http://c72QscMi.dncgb.cn
http://32snjrwM.dncgb.cn
http://yqt5g9CX.dncgb.cn
http://pKm8d5kv.dncgb.cn
http://nlrpgiq3.dncgb.cn
http://DvpeL9Q2.dncgb.cn
http://ErsOfuGG.dncgb.cn
http://TwKSMZan.dncgb.cn
http://BSgrsHoT.dncgb.cn
http://lAgSoBCp.dncgb.cn
http://3JHFcoBM.dncgb.cn
http://Uc6xThV9.dncgb.cn
http://QVjqAYn7.dncgb.cn
http://QqWsmfUW.dncgb.cn
http://www.dtcms.com/a/388030.html

相关文章:

  • Java的jdk21与 Go语言对比
  • 告别 MaaS 模型选型困难:AI Ping 为大模型服务选型提供精准性能评测排行榜
  • 41.OpenCV入门:计算机视觉的瑞士军刀
  • 初识golang
  • UE5 the “XXX“plugin was designed for build XXX,Attempt to load it anyway
  • docker快速安装环境
  • 如何安装TraeCN(字节跳动的IDE)使用AI IDE书写Vue3数据可视化大屏项目
  • Spark NLP: 最先进的自然语言处理和LLM库
  • 基于国产银河麒麟服务器SP3项目实战(Nginx+Keepalive)实现高可用负载均衡
  • 每日随机展示10个wordpress置顶文章
  • Leecode hot100 - 303. 区域和检索
  • 【审计试题案例】
  • 深度学习基础:线性回归与Softmax回归全面解析
  • C语言Prj03 运行显示乱码的解决方案
  • 车载操作系统总体技术要求解析
  • Spring Boot + MyBatis 实现站位标记系统实战
  • 读取X射线DICOM图像时需注意MONOCHROME1和PixelSpacing
  • mp4格式分析
  • LeetCode 1471.数组中的k个最强值
  • 基于R语言的水文、水环境模型优化技术及快速率定方法与多模型案例实践
  • python的守护线程设置
  • LTC5591IUH#TRPBF 无线和射频集成电路IC ADI亚德诺半导体 电子元器件解析
  • 【数据分享】土地利用shp数据分享-海南
  • 分布式拜占庭容错算法——PBFT算法深度解析
  • 《兔兔秘密花园》情人节密技曝光 输入隐藏指令即可
  • SQuAD:机器阅读理解领域的里程碑数据集
  • qt模型视图架构使用时需要注意什么
  • webRTC golang 开发核心
  • UVa10603 Fill
  • 小说《灵渊纪元:数据重构天道》的深层解读与象征意义分析