掘金 后端 ( ) • 2024-05-03 09:26

关注微信公众号:Linux内核拾遗

文章来源:https://mp.weixin.qq.com/s/GZ8ekqzLK_zXEwR5p7bLeA

1 Waitqueue介绍

Linux内核中事件等待是很普遍的事情,例如当进程必须等待某个事件发生后才能继续往下执行时,它通常需要暂停执行、让出CPU处理器资源,并进入睡眠状态。直到它等待的事件发生的时候,该进程会被唤醒然后继续往下执行。

Linux内核提供了多种方式分别在不同场景或者目的下处理进程睡眠和唤醒。Waitqueue(等待队列)是其中的一种方式。

Waitqueue作为一种重要的同步机制,用于实现多个进程之间的协作。等待队列允许一个或多个进程等待某个条件成立,然后在条件满足时被唤醒。这种机制通常用于进程间的同步和通信,例如在设备驱动程序中等待设备状态的改变或在并发处理中进行同步操作。

2 Waitqueue使用步骤

Linux设备驱动程序中使用Waitqueue,通常涉及以下三个关键步骤:

  1. 初始化Waitqueue;
  2. 将任务放置到Waitqueue中,睡眠直到事件发生;
  3. 当等待的事件发生时,从Waitqueue中唤醒任务。

Waitqueue相关API定义在linux/wait.h头文件中。

2.1 初始化等待队列

Waitqueue支持两种初始化方式:静态方式和动态方式。

// 1. 静态方式
DECLARE_WAIT_QUEUE_HEAD(wq);

// 2. 动态方式
wait_queue_head_t wq;
init_waitqueue_head (&wq);

2.2 任务入队

加入Waitqueue中的任务都会进入睡眠状态。根据入队方式的不同,队中任务有不同的唤醒行为:

2.2.1 wait_event

当满足特定条件时,当前入队的任务(TASK_UNINTERRUPTIBLE)会被唤醒:

wait_event(wq, condition);

其中wq时要加入的等待队列,condition是C语言表达式表示的等待条件。

每次唤醒等待队列wq时,wq中的所有等待条件condition都会被检查一遍,一旦取值为true,则将对应的任务唤醒,并从wq中移除。

2.2.2 wait_event_timeout

当满足特定条件,或者timeout超时时,当前入队的任务(TASK_UNINTERRUPTIBLE)会被唤醒:

wait_event_timeout(wq, condition, timeout);

相比wait_event,使用wait_event_timeout方式入队的任务,其唤醒条件多了一个超时时间(取值单位是jiffies)。

wait_event_timeout的返回值反映了具体的唤醒条件满足情况:

  • 0:timeout超时,且condition为false;
  • 1:timeout超时,且condition为true;
  • 剩余的jiffies:timeout超时前,condition为true。

2.2.3 wait_event_cmd

wait_event_cmd(wq, condition, cmd1, cmd2);

当前入队的任务(TASK_UNINTERRUPTIBLE)唤醒条件与wait_event相同,任务睡眠前执行cmd1,任务唤醒后执行cmd2。

2.2.4 wait_event_interruptible

当满足特定条件或者收到信号时,当前入队的任务(TASK_INTERRUPTIBLE)会被唤醒:

wait_event_interruptible(wq, condition);

返回值**-ERESTARTSYS**表示当前入队的任务被信号中断,为0表示是condition为true导致任务被唤醒。

2.2.5 wait_event_interruptible_timeout

wait_event_interruptible_timeout(wq, condition, timeout);

当前入队的任务(TASK_INTERRUPTIBLE)唤醒条件比wait_event_interruptible多了个超时机制。

2.2.6 wait_event_killable

wait_event_killable(wq, condition);

当前入队的任务(TASK_KILLABLE)唤醒条件同wait_event_interruptible。

2.3 队中任务唤醒

根据任务进入等待队列的不同方式,需要有不同的唤醒方式。

2.3.1 wake_up

唤醒等待队列中的一个以非可中断方式(TASK_UNINTERRUPTIBLE)睡眠的任务:

wake_up(&wq);

2.3.2 wake_up_all

唤醒等待队列中所有的任务:

wake_up_all(&wq);

2.3.3 wake_up_interruptible

唤醒等待队列中的一个以可中断方式(TASK_INTERRUPTIBLE)睡眠的任务:

wake_up_interruptible(&wq);

2.3.4 wake_up_sync/wake_up_interruptible_sync

wake_up_sync(&wq);
wake_up_interruptible_sync(&wq);

通常wake_up唤醒任务可能会导致立即发生任务调度,也就意味着被唤醒的任务可能在wake_up返回之前被调度执行。

因此带_sync尾缀的wake_up变体,可以使得被唤醒的任务变成可运行状态,但不重新调度CPU。

3 Waitqueue示例

下面的示例代码通过静态或者动态方式创建了一个等待队列my_waitqueue,并且在新的内核线程中通过while(1)循环反复等待特定的事件发生(waitqueue_flag != 0),然后打印计数值read_count。如果waitqueue_flag取值为2,则表示内核模块将要退出,因此跳出while(1)循环并结束执行。

3.1 静态创建

statically_create_waitqueue.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/err.h>

uint32_t read_count = 0;
static struct task_struct *wait_thread;

DECLARE_WAIT_QUEUE_HEAD(my_waitqueue);

dev_t dev = 0;
static struct class *dev_class;
static struct cdev my_cdev;
int waitqueue_flag = 0;

static int wait_function(void *unused)
{

    while (1)
    {
        pr_info("Waiting For Event...\n");
        wait_event_interruptible(my_waitqueue, waitqueue_flag != 0);
        if (waitqueue_flag == 2)
        {
            pr_info("Event Came From Exit Function\n");
            return 0;
        }
        pr_info("Event Came From Read Function - %d\n", ++read_count);
        waitqueue_flag = 0;
    }
    do_exit(0);
    return 0;
}

static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    waitqueue_flag = 1;
    wake_up_interruptible(&my_waitqueue);
    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = my_read,
};

static int __init my_driver_init(void)
{
    if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0)
        return -1;

    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;
    my_cdev.ops = &fops;

    if ((cdev_add(&my_cdev, dev, 1)) < 0)
        goto r_class;

    if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class")))
        goto r_class;

    if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device")))
        goto r_device;

    if ((wait_thread = kthread_create(wait_function, NULL, "WaitThread")))
        wake_up_process(wait_thread);

    return 0;

r_device:
    class_destroy(dev_class);
r_class:
    unregister_chrdev_region(dev, 1);
    return -1;
}

static void __exit my_driver_exit(void)
{
    waitqueue_flag = 2;
    wake_up_interruptible(&my_waitqueue);
    device_destroy(dev_class, dev);
    class_destroy(dev_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev, 1);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("feifei <[email protected]>");
MODULE_DESCRIPTION("Simple linux driver");
MODULE_VERSION("1.7");

3.2 动态创建

dynamically_create_waitqueue.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/err.h>

uint32_t read_count = 0;
static struct task_struct *wait_thread;

dev_t dev = 0;
static struct class *dev_class;
static struct cdev my_cdev;
wait_queue_head_t my_waitqueue;
int waitqueue_flag = 0;

static int wait_function(void *unused)
{

    while (1)
    {
        pr_info("Waiting For Event...\n");
        wait_event_interruptible(my_waitqueue, waitqueue_flag != 0);
        if (waitqueue_flag == 2)
        {
            pr_info("Event Came From Exit Function\n");
            return 0;
        }
        pr_info("Event Came From Read Function - %d\n", ++read_count);
        waitqueue_flag = 0;
    }
    do_exit(0);
    return 0;
}

static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    waitqueue_flag = 1;
    wake_up_interruptible(&my_waitqueue);
    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = my_read,
};

static int __init my_driver_init(void)
{
    if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0)
        return -1;

    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;
    my_cdev.ops = &fops;

    if ((cdev_add(&my_cdev, dev, 1)) < 0)
        goto r_class;

    if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class")))
        goto r_class;

    if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device")))
        goto r_device;

    init_waitqueue_head(&my_waitqueue);

    if ((wait_thread = kthread_create(wait_function, NULL, "WaitThread")))
        wake_up_process(wait_thread);

    return 0;

r_device:
    class_destroy(dev_class);
r_class:
    unregister_chrdev_region(dev, 1);
    return -1;
}

static void __exit my_driver_exit(void)
{
    waitqueue_flag = 2;
    wake_up_interruptible(&my_waitqueue);
    device_destroy(dev_class, dev);
    class_destroy(dev_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev, 1);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("feifei <[email protected]>");
MODULE_DESCRIPTION("Simple linux driver");
MODULE_VERSION("1.7");

3.3 代码演示

image-20240501210613833

关注微信公众号:Linux内核拾遗

文章来源:https://mp.weixin.qq.com/s/GZ8ekqzLK_zXEwR5p7bLeA