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

本期主题:
讲清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来运行
实例讲解,一文弄懂workqueue和waitqueue_第1张图片

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的信息
实例讲解,一文弄懂workqueue和waitqueue_第2张图片

2. workstruct定义

其中的func就是work_item的callback函数,entry就是添加到前面的workqueue_strcut的pwqs list中,然后遍历
实例讲解,一文弄懂workqueue和waitqueue_第3张图片

3. queue_delayed_work

实例讲解,一文弄懂workqueue和waitqueue_第4张图片

实例讲解,一文弄懂workqueue和waitqueue_第5张图片

example

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

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

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");

测试结果:
实例讲解,一文弄懂workqueue和waitqueue_第6张图片

3.waitqueue

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

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

Linux上waitqueue例子

waitqueue API

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

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

1. waitqueue 结构体
实例讲解,一文弄懂workqueue和waitqueue_第7张图片

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

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

  1. 静态初始化: DECLARE_WAITQUEUE
  2. 动态初始化:init_waitqueue_head
    实例讲解,一文弄懂workqueue和waitqueue_第8张图片

3. 将进程加入等待队列

wait_event(wq_head, condition)
实例讲解,一文弄懂workqueue和waitqueue_第9张图片
等待事件接口,sleep直至conditon满足

其中,__wait_event的代码如下:
实例讲解,一文弄懂workqueue和waitqueue_第10张图片

wait的返回值
实例讲解,一文弄懂workqueue和waitqueue_第11张图片

4. wakeup
实例讲解,一文弄懂workqueue和waitqueue_第12张图片

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

example

例子讲解:

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

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
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才真正退出
实例讲解,一文弄懂workqueue和waitqueue_第13张图片

4.总结

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

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

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

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

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

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

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

你可能感兴趣的:(linux设备驱动开发,计算机操作系统,linux,运维,服务器,驱动开发)