本文是Linux设备驱动程序系列的续篇,并对字符驱动程序及其实现进行了讨论。这是Linux设备驱动程序教程的第10部分。现在,我们将讨论Linux中的Waitqueue。
Waitqueue in Linux
Linux中的等待队列
介绍
当您编写Linux驱动程序,模块或内核程序时,某些进程应为某些事件等待或休眠。在Linux中有几种处理睡眠和唤醒的方法,每种方法都适合不同的需求。 Waitqueue也是处理这种情况的方法之一。
每当进程必须等待事件(例如数据到达或进程终止)时,它就应该进入睡眠状态。睡眠会导致进程挂起执行,将处理器释放出来以用于其他用途。一段时间后,该过程将被唤醒,并在我们等待的事件到达时继续其工作。
Waitqueue中有3个重要步骤。
- 初始化等待队列
- 排队(使任务进入睡眠状态,直到事件发生)
- 唤醒排队的任务
初始化等待队列
将此头文件用于Waitqueue(#include /linux/wait.h
)。有两种初始化等待队列的方法。
- 静态方法
- 动态方法
您可以使用任何一种方法。
静态方法
DECLARE_WAIT_QUEUE_HEAD(wq);
其中“ wq”是将使任务进入休眠状态的队列的名称。
动态方法
wait_queue_head_t wq;
init_waitqueue_head(&wq);
您可以使用上述任何一种方法来创建等待队列。
排队
一旦声明并初始化了等待队列,进程便可以使用它进入睡眠状态。有几个宏可用于不同用途。我们将一一看到。
wait_event
wait_event_timeout
wait_event_cmd
wait_event_interruptible
wait_event_interruptible_timeout
wait_event_killable
旧的内核版本使用函数sleep_on()
和interruptible_sleep_on()
,但是这两个函数会引入不良的竞争条件,因此不应使用。
每当我们使用上面的宏之一时,它将把该任务添加到由我们创建的等待队列中。然后它将等待事件。
wait_event
休眠直到条件成真。
wait_event(wq, condition);
wq:等待的等待队列
condition:等待事件的C表达式
进程进入睡眠状态(TASK_UNINTERRUPTIBLE
),直到条件评估为true
。每次唤醒等待队列wq
时都会检查条件。
wait_event_timeout
休眠直到条件成真或者超时。
wait_event_timeout(wq, condition, timeout);
wq:等待的等待队列
condition:等待事件的C表达式
timeout – 超时, 单位jiffies
进程进入睡眠状态(TASK_UNINTERRUPTIBLE
),直到条件评估为true
或者超时。每次唤醒等待队列wq
时都会检查条件。
如果在超时后将条件评估为假,则返回0;如果在超时后将条件评估为真,则返回1;如果在超时之前将条件评估为真,则返回剩余的jiffies(至少1)。
wait_event_cmd
休眠直到条件成真。
wait_event_cmd(wq, condition, cmd1, cmd2);
wq:等待的等待队列
condition:等待事件的C表达式
cmd1:该命令将在睡眠前执行
cmd2:该命令将在睡眠后执行
进程进入睡眠状态(TASK_UNINTERRUPTIBLE
),直到条件评估为true
。每次唤醒等待队列wq
时都会检查条件。
wait_event_interruptible
休眠直到条件成真。
wait_event_interruptible(wq, condition);
wq:等待的等待队列
condition:等待事件的C表达式
cmd1:该命令将在睡眠前执行
cmd2:该命令将在睡眠后执行
进程进入睡眠状态(TASK_UNINTERRUPTIBLE
),直到条件评估为true
。每次唤醒等待队列wq
时都会检查条件。
如果该函数被信号中断,则该函数将返回-ERESTARTSYS;如果条件被评估为true,则该函数将返回0。
wait_event_interruptible_timeout
休眠直到条件成真或者超时。
wait_event_timeout(wq, condition, timeout);
wq:等待的等待队列
condition:等待事件的C表达式
timeout – 超时, 单位jiffies
进程进入睡眠状态(TASK_INTERRUPTIBLE
),直到条件评估为true
或收到信号或超时超时。每次唤醒等待队列wq
时都会检查条件。
返回:
如果在超时后将条件评估为false,则返回0;
如果在超时后将条件评估为true,则返回1;
如果在超时之前将条件评估为true,则返回剩余的抖动(至少1),或者返回-ERESTARTSYS它被信号打断了。
wait_event_killable
休眠直到条件成真
wait_event_killable(wq, condition);
wq:等待的等待队列
condition:等待事件的C表达式
该过程进入睡眠状态(TASK_KILLABLE
),直到条件评估为true
或收到信号为止。每次唤醒等待队列wq
时都会检查条件。
如果该函数被信号中断,则该函数将返回-ERESTARTSYS
;如果条件被评估为true
,则该函数将返回0
。
唤醒等待的任务
当某些任务由于等待队列而进入睡眠模式时,我们可以使用以下功能唤醒这些任务。
wake_up
wake_up_all
wake_up_interruptible
wake_up_sync and wake_up_interruptible_sync
wake_up
从处于不间断睡眠的等待队列中仅唤醒一个进程。
wake_up(&wq);
wq:唤醒的等待队列
wake_up_all
唤醒等待队列中的所有进程
wake_up_all(&wq);
wq:唤醒的等待队列
wake_up_interruptible
从处于中断睡眠状态的等待队列中仅唤醒一个进程
wake_up_interruptible(&wq);
wq:唤醒的等待队列
wake_up_sync and wake_up_interruptible_sync
wake_up_sync(&wq);
wake_up_interruptible_sync(&wq);
通常,wake_up
调用会导致立即重新安排,这意味着其他进程可能在wake_up
返回之前运行。相反,“同步”变体使所有唤醒的进程都可运行,但不会重新调度CPU
。这用于避免在已知当前进程即将进入睡眠状态时进行重新计划,从而仍然强制重新计划。请注意,唤醒的进程可以立即在其他处理器上运行,因此不应期望这些功能提供相互排斥。
WaitQueue in Linux
首先,我将向您解释驱动程序代码的概念。
在此源代码中,我们在两个地方发送了一个wake_up
。一个来自读取功能,另一个来自驱动程序退出功能。
我创建了一个具有while(1)的线程(wait_function)。该线程将始终等待事件。它将一直休眠,直到得到一个wake_up调用。当它获得了wake_up调用时,它将检查条件。如果条件为1,则唤醒来自读取功能。如果为2,则唤醒来自退出功能。如果wake_up来自读取函数,它将打印读取计数并再次等待。如果来自退出函数,它将从线程退出。
在这里,我添加了两个版本的代码。
- 静态方法创建的等待队列
- 通过动态方法创建的等待队列
通过静态方法创建的等待队列
/***************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Waitqueue Static method)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#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> //kmalloc()
#include <linux/uaccess.h> //copy_to/from_user()
#include <linux/kthread.h>
#include <linux/wait.h> // Required for the wait queues
uint32_t read_count = 0;
static struct task_struct *wait_thread;
DECLARE_WAIT_QUEUE_HEAD(wait_queue_etx);
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
int wait_queue_flag = 0;
/*
** Function Prototypes
*/
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
/*************** Driver Fuctions **********************/
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off);
/*
** File operation sturcture
*/
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.release = etx_release,
};
/*
** Thread function
*/
static int wait_function(void *unused)
{
while(1) {
printk(KERN_INFO "Waiting For Event...\n");
wait_event_interruptible(wait_queue_etx, wait_queue_flag != 0 );
if(wait_queue_flag == 2) {
printk(KERN_INFO "Event Came From Exit Function\n");
return 0;
}
printk(KERN_INFO "Event Came From Read Function - %d\n", ++read_count);
wait_queue_flag = 0;
}
do_exit(0);
return 0;
}
/*
** This fuction will be called when we open the Device file
*/
static int etx_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Opened...!!!\n");
return 0;
}
/*
** This fuction will be called when we close the Device file
*/
static int etx_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Closed...!!!\n");
return 0;
}
/*
** This fuction will be called when we read the Device file
*/
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Read Function\n");
wait_queue_flag = 1;
wake_up_interruptible(&wait_queue_etx);
return 0;
}
/*
** This fuction will be called when we write the Device file
*/
static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Write function\n");
return 0;
}
/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
printk(KERN_INFO "Cannot allocate major number\n");
return -1;
}
printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
/*Creating cdev structure*/
cdev_init(&etx_cdev,&fops);
etx_cdev.owner = THIS_MODULE;
etx_cdev.ops = &fops;
/*Adding character device to the system*/
if((cdev_add(&etx_cdev,dev,1)) < 0){
printk(KERN_INFO "Cannot add the device to the system\n");
goto r_class;
}
/*Creating struct class*/
if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
printk(KERN_INFO "Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
printk(KERN_INFO "Cannot create the Device 1\n");
goto r_device;
}
//Initialize wait queue
init_waitqueue_head(&wait_queue_etx);
//Create the kernel thread with name 'mythread'
wait_thread = kthread_create(wait_function, NULL, "WaitThread");
if (wait_thread) {
printk("Thread Created successfully\n");
wake_up_process(wait_thread);
} else
printk(KERN_INFO "Thread creation failed\n");
printk(KERN_INFO "Device Driver Insert...Done!!!\n");
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev,1);
return -1;
}
/*
** Module exit function
*/
static void __exit etx_driver_exit(void)
{
wait_queue_flag = 2;
wake_up_interruptible(&wait_queue_etx);
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple linux driver (Waitqueue Static method)");
MODULE_VERSION("1.7");
通过动态方法创建等待队列
/****************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Waitqueue Dynamic method)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#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> //kmalloc()
#include <linux/uaccess.h> //copy_to/from_user()
#include <linux/kthread.h>
#include <linux/wait.h> // Required for the wait queues
uint32_t read_count = 0;
static struct task_struct *wait_thread;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
wait_queue_head_t wait_queue_etx;
int wait_queue_flag = 0;
/*
** Function Prototypes
*/
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
/*************** Driver Fuctions **********************/
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off);
/*
** File operation sturcture
*/
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.release = etx_release,
};
/*
** Thread function
*/
static int wait_function(void *unused)
{
while(1) {
printk(KERN_INFO "Waiting For Event...\n");
wait_event_interruptible(wait_queue_etx, wait_queue_flag != 0 );
if(wait_queue_flag == 2) {
printk(KERN_INFO "Event Came From Exit Function\n");
return 0;
}
printk(KERN_INFO "Event Came From Read Function - %d\n", ++read_count);
wait_queue_flag = 0;
}
return 0;
}
/*
** This fuction will be called when we open the Device file
*/
static int etx_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Opened...!!!\n");
return 0;
}
/*
** This fuction will be called when we close the Device file
*/
static int etx_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Closed...!!!\n");
return 0;
}
/*
** This fuction will be called when we read the Device file
*/
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Read Function\n");
wait_queue_flag = 1;
wake_up_interruptible(&wait_queue_etx);
return 0;
}
/*
** This fuction will be called when we write the Device file
*/
static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Write function\n");
return 0;
}
/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
printk(KERN_INFO "Cannot allocate major number\n");
return -1;
}
printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
/*Creating cdev structure*/
cdev_init(&etx_cdev,&fops);
/*Adding character device to the system*/
if((cdev_add(&etx_cdev,dev,1)) < 0){
printk(KERN_INFO "Cannot add the device to the system\n");
goto r_class;
}
/*Creating struct class*/
if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
printk(KERN_INFO "Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
printk(KERN_INFO "Cannot create the Device 1\n");
goto r_device;
}
//Initialize wait queue
init_waitqueue_head(&wait_queue_etx);
//Create the kernel thread with name 'mythread'
wait_thread = kthread_create(wait_function, NULL, "WaitThread");
if (wait_thread) {
printk("Thread Created successfully\n");
wake_up_process(wait_thread);
} else
printk(KERN_INFO "Thread creation failed\n");
printk(KERN_INFO "Device Driver Insert...Done!!!\n");
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev,1);
return -1;
}
/*
** Module exit function
*/
static void __exit etx_driver_exit(void)
{
wait_queue_flag = 2;
wake_up_interruptible(&wait_queue_etx);
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple linux driver (Waitqueue Dynamic method)");
MODULE_VERSION("1.8");
MakeFile
obj-m += driver.o
KDIR = /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(shell pwd) modules
clean:
make -C $(KDIR) M=$(shell pwd) clean
编译测试
make
insmod driver.ko
dmesg
因此该线程正在等待事件。现在我们将通过使用
sudo cat /dev/etx_device
读取驱动程序来发送事件
我们发送来自读取功能的唤醒,因此它将打印读取计数,然后再次进入睡眠状态。现在通过sudo rmmod驱动程序从退出函数发送事件
现在条件是2。因此它将从线程返回并删除驱动程序。
这就是等待队列。在下一个教程中,我们将讨论Linux设备驱动程序中的sysfs。