写点什么

Linux 设备驱动系列(十)——等待队列 Waitqueue

  • 2024-05-02
    山东
  • 本文字数:4181 字

    阅读完需:约 14 分钟

Linux设备驱动系列(十)——等待队列Waitqueue

关注微信公众号: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 <feifei@gmail.com>");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 <feifei@gmail.com>");MODULE_DESCRIPTION("Simple linux driver");MODULE_VERSION("1.7");
复制代码

3.3 代码演示


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

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


发布于: 刚刚阅读数: 5
用户头像

聚沙成塔 2023-01-12 加入

分享Linux内核开发相关的编程语言、开发调试工具链、计算机组成及操作系统内核知识、Linux社区最新资讯等

评论

发布
暂无评论
Linux设备驱动系列(十)——等待队列Waitqueue_队列_Linux内核拾遗_InfoQ写作社区