实例讲解,一文弄懂workqueue和waitqueue

news2025/1/12 1:46:44

本期主题:
讲清workqueue和waitqueu:

  1. 从中断讲起
  2. waitqueue是什么
  3. workqueue
  4. 总结

往期链接:

  • linux设备驱动中的并发
  • linux设备驱动中的编译乱序和执行乱序
  • linux设备驱动之内核模块
  • linux字符驱动
  • linux字符驱动之ioctl部分
  • linux字符驱动之read、write部分
  • linux驱动调试之Debugfs
  • Linux下如何操作寄存器(用户空间、内核空间方法讲解)
  • petalinux的module编译

目录

  • 1.从中断讲起
  • 2.workqueue
    • Linux上workqueue例子
      • workqueue API
      • example
  • 3.waitqueue
    • Linux上waitqueue例子
      • waitqueue API
    • example
  • 4.总结


1.从中断讲起

从操作系统角度而言,实际上有两种中断:

  1. 硬件中断(hardware IRQ),由系统自身和外设产生的中断,例如在ARM侧就是GIC中断;
  2. 软中断(software IRQ),用于实现延时执行操作(这是重点,后面会有解释);

我们来分析一下 中断处理程序 的需求

需求:

  1. 中断处理程序需要尽可能简短,这样能够快速处理中断;
  2. 允许在处理A中断时,被B中断打断然后去处理B的事情,允许中断的嵌套;

由于有快速、简短这样的需求,所以操作系统设计出了这样的机制:

  1. 关键操作(可以理解为清除一些中断状态位的操作),必须在中断发生后立刻去做;
  2. 非关键操作也应尽快执行,但允许启用中断;
  3. 可延期操作不是特别重要,不必在中断处理程序中运行,可以延迟这些操作,在时间充裕时进行;

软中断就是这样的一个作用,可以使操作延时去做

2.workqueue

workqueue顾名思义就是工作队列,他有几个特点:

  1. 处理不那么紧急的任务,我们常说的Linux中断处理的底半部就可以使用workqueue来做;
  2. 函数执行环境的切换,从中断环境切到了线程环境
  3. 总而言之,workqueue适用于异步执行的场景;

workqueue就相当于在worklist(工作链表)上挂上了一个个的work item(工作节点),每次都从中取出这些item来运行
在这里插入图片描述

Linux上workqueue例子

workqueue API

讲解以下几个API:

  1. workqueue的结构体定义
  2. work_struct的结构体定义(work item)
  3. delay_work接口

1. workqueue定义:

//kernel/workqueue.c
其中的pwqs是 pool workqueues,代表着这个workqueue所管理的pool,pool在这里的意思理解是一个池子,存放着当前wq的信息
在这里插入图片描述

2. workstruct定义

其中的func就是work_item的callback函数,entry就是添加到前面的workqueue_strcut的pwqs list中,然后遍历
在这里插入图片描述

3. queue_delayed_work

在这里插入图片描述

在这里插入图片描述

example

看一个例子,我们写一个ko,实现的功能如下:

  1. 在init的时候创建workqueue,并且添加delay work,delay时间设置为1s
  2. 1s之后,调用我们的work handler,做一个异步处理的典型场景
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>

static void mykmod_work_handler(struct work_struct *w);

static struct workqueue_struct *wq = 0;
static DECLARE_DELAYED_WORK(mykmod_work, mykmod_work_handler);
static unsigned long onesec;

static void
mykmod_work_handler(struct work_struct *w)
{
        pr_info("mykmod work %u jiffies\n", (unsigned)onesec);
}


static int mykmod_init(void)
{
        onesec = msecs_to_jiffies(1000);
        pr_info("mykmod loaded %u jiffies\n", (unsigned)onesec);

        wq = create_singlethread_workqueue("mykmod");
        if (!wq)
                pr_err("wq init failed!\n");

        queue_delayed_work(wq, &mykmod_work, onesec);

        return 0;
}

static void mykmod_exit(void)
{
        if (wq) {
                cancel_delayed_work_sync(&mykmod_work);
                destroy_workqueue(wq);
        }
        pr_info("mykmod exit\n");
}

module_init(mykmod_init);
module_exit(mykmod_exit);

MODULE_DESCRIPTION("mykmod");
MODULE_LICENSE("GPL");

测试结果:
在这里插入图片描述

3.waitqueue

等待队列用于在进程中等待某一特定事件发生,无须频繁轮询。进程在等待期间休眠,事件发生时,内核唤醒进程。总结一下有几个特点:

  1. 进程等不到时会休眠;
  2. 事件发生时,内核会把进程唤醒;

Linux上waitqueue例子

waitqueue API

介绍waitqueue结构体以及下面三种API:

1.waitqueue结构体
2. 初始化等待队列,DECLARE_WAIT_QUEUE_HEAD
3. 将进程加入等待队列,wait_event
4. 唤醒等待队列中的进程,wakeup

1. waitqueue 结构体
在这里插入图片描述

  1. waitqueue head只有一个自旋锁和双向链表;
  2. wait_queue_entry是对一个等待任务的抽象,每个等待任务都会抽象成一个wait_queue_t,并且挂载到wait_queue_head_t上;

2. 初始化等待队列
初始化等待队列,实际上就是waitqueue_head的初始化,可以使用静态初始化也可使用动态初始化;

  1. 静态初始化: DECLARE_WAITQUEUE
  2. 动态初始化:init_waitqueue_head
    在这里插入图片描述

3. 将进程加入等待队列

wait_event(wq_head, condition)
在这里插入图片描述
等待事件接口,sleep直至conditon满足

其中,__wait_event的代码如下:
在这里插入图片描述

4. wakeup
在这里插入图片描述

通过遍历wq_head来找到entry,并且调用entry的func;

example

例子讲解:

  1. 创建一个静态等待队列,并在Moudule init的时候建立一个内核进程;
  2. 注册好proc_fs,上层通过Cat读取/proc文件时,会将flag置1,并wakeup等待进程;
  3. 在rmmod 模块时,会将flag置2,并wakup等待进程,此时内核进程退出;

下面是代码以及测试结果:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/wait.h>
#include <linux/kthread.h>
static int read_count = 0;
static struct task_struct *wait_thread;
// Initializing waitqueue statically
DECLARE_WAIT_QUEUE_HEAD(test_waitqueue);
static int wait_queue_flag = 0;
static int my_waitqueue_show(struct seq_file *m, void *v)
{
        printk(KERN_ALERT "Read function\n");
        seq_printf(m, "read_count = %d\n", read_count);
        wait_queue_flag = 1;
        wake_up_interruptible(&test_waitqueue); // wake up only one process from wait queue
        return 0;
}
static int my_waitqueue_open(struct inode *inode, struct file *filp)
{
        return single_open(filp, my_waitqueue_show, NULL);
}
static struct proc_fs test_wait_queue_fops = {
        .proc_open           = my_waitqueue_open,
        .proc_read           = seq_read,
        .proc_lseek         = seq_lseek,
        .proc_release        = single_release,
};
static int wait_function(void *unused)
{
        while(1) {
                printk(KERN_ALERT "Waiting For Event...\n");
                // sleep until wait_queue_flag != 0
                wait_event_interruptible(test_waitqueue, wait_queue_flag != 0);
                if (wait_queue_flag == 2) {
                        printk(KERN_ALERT "Event Came From Exit Function\n");
                        return 0;
                }
                printk(KERN_ALERT "Event Came From Read Function - %d\n", ++read_count);
                wait_queue_flag = 0;
        }
        return 0;
}
static int mywaitqueue_init(void)
{
        struct proc_dir_entry *pe;
        printk(KERN_ALERT "[Hello] mywaitqueue \n");
        pe = proc_create("test_wait_queue", 0644, NULL, &test_wait_queue_fops);
        if (!pe)
                return -ENOMEM;
        // Create the kernel thread with name "MyWaitThread"
        wait_thread = kthread_create(wait_function, NULL, "MyWaitThread");
        if (wait_thread) {
                printk(KERN_ALERT "Thread created successfully\n");
                wake_up_process(wait_thread);
        } else {
                printk(KERN_ALERT "Thread creation failed\n");
        }
        return 0;
}
static void mywaitqueue_exit(void)
{
        wait_queue_flag = 2;
        wake_up_interruptible(&test_waitqueue);
        printk(KERN_ALERT "[Goodbye] mywaitqueue\n");
        remove_proc_entry("test_wait_queue", NULL);
}
module_init(mywaitqueue_init);
module_exit(mywaitqueue_exit);
MODULE_LICENSE("GPL");

测试结果:
进行了3次cat读取,最后rmmod才真正退出
在这里插入图片描述

4.总结

waitqueueworkqueue 是实时操作系统中用于线程间通信和任务调度的两种机制,它们有一些区别和不同的应用场景。

waitqueue 是一种线程等待队列,用于实现线程间的同步和通信。它允许线程等待某个条件的发生,并在条件满足时被唤醒继续执行。通过将线程放入等待队列中,可以避免线程在条件不满足时的忙等待,提高系统的效率和资源利用率。waitqueue 主要用于实现线程间的同步和事件通知,例如一个线程等待某个资源的可用性或某个事件的发生。

workqueue 是一种任务队列,用于调度和执行后台任务。它可以在后台异步执行一些耗时的操作,而不阻塞当前线程的执行。workqueue 将任务添加到队列中,并由系统自动调度和执行。相比于直接在当前线程中执行任务,使用 workqueue 可以将任务的执行延迟到后台,提高系统的响应性和并发性。workqueue 主要用于处理一些耗时的、非实时性的任务,例如文件系统操作、网络请求、数据处理等。

下面是 waitqueueworkqueue 的一些主要差异:

  1. 功能目的:waitqueue 用于线程间的同步和通信,而 workqueue 用于后台任务的调度和执行。

  2. 调度方式:waitqueue 中的线程需要显式地被唤醒,可以通过条件的满足或其他线程的通知来触发唤醒操作;而 workqueue 中的任务由系统自动调度,无需显式触发。

  3. 使用场景:waitqueue 主要用于实现同步和事件通知的场景,例如等待某个资源的可用性或某个事件的发生;而 workqueue 主要用于执行后台任务,例如文件系统操作、网络请求、数据处理等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/679882.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构07:查找[C++][朴素二叉排序树BST]

图源&#xff1a;文心一言 考研笔记整理8k字&#xff0c;小白友好、代码可跑&#xff0c;请小伙伴放心食用~~&#x1f95d;&#x1f95d; 第1版&#xff1a;查资料、写BUG、画导图、画配图~&#x1f9e9;&#x1f9e9; 参考用书&#xff1a;王道考研《2024年 数据结构考研复习…

【MATLAB第45期】基于MATLAB的深度学习SqueezeNet卷积神经网络混凝土裂纹图像识别预测模型

【MATLAB第45期】基于MATLAB的深度学习SqueezeNet卷积神经网络混凝土裂纹图像识别预测模型 引言 该文章展示如何微调名为SqueezeNet的预训练深度卷积网络&#xff0c;以执行裂纹图像分类预测。并使用一种称为Grad-CAM的技术来解释和分析分类输出。文章使用L.Zhang介绍的混凝土…

C++初阶之C++入门最全详解

C入门 1. C关键字&#xff08;C98&#xff09;2. 命名空间2.1 命名空间定义2.2 命名空间使用 3. C输入&输出4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类 5. 函数重载5.1 函数重载概念5.1.1 参数类型不同5.1.2 参数个数不同5.1.3 参数类型顺序不同 5.2 C支持函数重载的原理…

Spring Cloud Alibaba Seata(二)

目录 一、Seata 1、Seata-AT模式 1.1、具体案例 1.2、通过Seata的AT模式解决分布式事务 2、Seata-XA模式 3、Seata-TCC模式 4、Seata-SAGA模式 一、Seata 1、Seata-AT模式 概念&#xff1a;AT模式是一种无侵入的分布式事务解决方案&#xff0c;在 AT 模式下&#xff0c…

git修改默认主分支main为master和设置git默认创建的项目默认分支都为master

文章目录 前言一、设置新建仓库默认分支为master1.点击GitHub右上角的头像2. 选中settings&#xff08;设置&#xff09;3.点击Repositories&#xff08;存储库&#xff09;4.更改main为master后点击update 二、设置已建仓库的默认分支为master1.找到你要改的项目点击settings&…

STL序列式容器的概念

文章目录 1 迭代器2 什么是序列式容器3 序列式容器容器中常见的函数成员参考 1 迭代器 迭代器和C指针非常类似&#xff0c;它可以是需要的任意类型&#xff0c;通过迭代器可以指向容器中的某个元素&#xff0c;如果需要&#xff0c;还可以对该元素进行读写操作。 迭代器类别 …

ThreeJS案例一——在场景中添加视频,使用人物动作以及用键盘控制在场景中行走的动画

准备 首先我们需要两个模型&#xff0c;一个是场景模型&#xff0c;另一个是人物模型。 人物模型我这里用的Threejs官网中的给的模型&#xff0c;名称是Xbot.glb。 当然人物模型也可以自己去这个网站下载sketchfab&#xff0c;下载后给模型添加动画mixamo 下载模型动画 先让…

C++ STL vector容器用法

文章目录 1 vector初始化方法2 vector容器迭代器3 data()函数4 emplace_back()和push_back()的区别5 insert()函数6 vector删除元素参考 1 vector初始化方法 方式1&#xff1a; std::vector<double> values;//创建空的vcetor values.reserve(20); //设置容器的内存分配…

【实战】 JWT、用户认证与异步请求(1) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(四)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表三、TS 应用&#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求1.login2.middleware of json-server3.jira-dev-tool&#xff08;imooc-jira-tool&#xff09;安装问…

《流浪地球 2》的硬核黑科技

电影中&#xff0c;由刘德华饰演的量子计算机工程师图恒宇有一个惊心动魄的情节。为了同步启动全球地球发动机&#xff0c;需要重启互联网&#xff0c;避免地壳破碎和地质灾害。而重启互联网的关键则是要启动“根服务器”。电影中没有具体交代是什么根服务器&#xff0c;但是当…

Ubuntu18.04屏幕分辨率问题

本篇博客最早发布于实验室公共博客&#xff0c;但已无人维护&#xff0c;现迁移至个人博客 起因 本来昨天还好好的&#xff0c;过了一夜&#xff0c;就变了&#xff0c;像极了咳咳(自行脑补) redwallbot-2小车上固定的屏幕&#xff0c;屏幕分辨率本来应该是1920x1080的&#…

DevOps系列文章之 linux安装ftp

第一步 1、用root 进入系统 2、使用命令 rpm -qa|grep vsftpd 查看系统是否安装了ftp&#xff0c;若安装了vsftp&#xff0c;使用这个命令会在屏幕上显示vsftpd的版本 3、使用命令rpm -e vsftpd 即可卸载ftp 4、再使用rpm -qa|grep vsftpd 查看系统是否已删除ftp&#xff0…

【小技巧】vscode 在 JS 文件中补全 HTML标签

文章目录 vscode中有很多插件可以支持 HTML 标签自动补全&#xff0c;在.vue和.html文件中都没有问题&#xff0c;但是在使用react时&#xff0c;HTML标签是写在js或者是ts文件中&#xff0c;插件就不起作用了 解决方案&#xff1a; 在设置中插入这段设置代码 "emmet.i…

uniapp 微信小程序sourcemap映射

uniapp 微信小程序sourcemap映射 错误捕获 由于微信小程序中没有window对象&#xff0c;不能通过window.onerror和window.onunhandledRejection方法进行全局的监听。不过我们也可以使用以下几种方法。 使用try…catch 将可能出现的错误的代码使用try...catch包裹 try{cont…

【C++】一文读懂C++中的异常处理机制

文章目录 C 中的异常处理机制1.1 什么是异常&#xff1f;1.2 调用abort()1.3 返回错误码1.4 异常机制1.5 将对象用作异常类型1.6 异常规范和C111.7 栈解退1.7.1 return和throw的区别1.7.2 什么是栈解退 1.8 其他异常特性1.9 excepyion类1.9.1 stdexcept异常类1.9.2 bad_alloc异…

329款超有设计感的英文字体合集

一组超有设计感的英文字体合集&#xff0c;总共329个字库包含多种字体风格&#xff1a;手写字体、签名字体、复古字体、笔刷字体、漫画字体等无衬线字体。适用于签名、文具、标志、排版引言、杂志或书籍封面。素材获取&#xff1a;取括号内容&#xff0c;&#xff08;scwan&…

【 openGauss数据库】--运维指南01

【 openGauss数据库】--运维指南01 &#x1f53b; 一、 openGauss数据库运维指南&#x1f530; 1.1 启停openGauss&#x1f530; 1.2 查看openGauss数据库状态 &#x1f53b; 二、 维护检查项&#x1f530; 2.1 检查实例状态&#x1f530; 2.2 检查锁信息&#x1f530; 2.3 统计…

个人向非企业,基于目前主流图床的选购指南

1. 为什么需要搭建自己的图床 最近研究了一下国内外比较主流的图床与对象存储&#xff0c;因为个人写作更加偏向于使用Markdown&#xff0c;而国内很多平台如掘金&#xff0c;简书&#xff0c;csdn等等网站都做了相关的防盗链&#xff0c;即使是我为作者本人&#xff0c;想取用…

C语言里面那些你必须知道的常用关键字(详细讲解)

前言 哈喽&#xff0c;各位铁汁们好啊&#xff01;✨今天来给大家带来的是C语言中我们常用的关键字静态static的详细讲解和typedef 、#define定义常量和宏。   既然是详解想必大家必定是想学一些平常学不到的东西吧&#xff01;这里博主给大家详细讲解static修饰的变量在内存…

2023最全的Java架构师面试120题解析(MySQL/Redis/架构/高并发等)

最全架构师题目将包含如下技术范围&#xff1a; 1.Java基础和高级: 集合框架: List&#xff1a;ArrayList、LinkedList&#xff1b;Set&#xff1a;HashSet、TreeSet Map:TreeMap/ConcurrentHashMap&#xff1b;Queue:ConcurrentLinkedQueue等 泛型、反射、并发编程、JVM、A…