Linux kernel的wait queue机制

嵌入式技术

1343人已加入

描述

1. 介绍

当编写Linux驱动程序、模块或内核程序时,一些进程会等待或休眠一些事件。Linux中有几种处理睡眠和醒来的方法,每种方法对应不同的需求,而wait queue便是其中一种。

每当进程必须等待一个事件(例如数据的到达或进程的终止)时,它都应该进入睡眠状态。睡眠会导致进程暂停执行,从而释放处理器以供其他用途。一段时间后,该过程将被唤醒,并在我们等待的事件到达时继续其工作。

等待队列是内核提供的一种机制,用于实现等待。顾名思义,wait queue是等待事件的进程列表。换句话说,当某个条件成立时,等待队列用于等待有人叫醒你。它们必须小心使用,以确保没有竞争条件的存在。

实现wait queue的步骤如下:

初始化等待队列

排队(将任务置于睡眠状态,直到事件发生)

唤醒排队的任务

以下逐步介绍每个步骤的实现方式。

2. 初始化等待队列

若使用wait queue功能,需要包含/linux/wait.h头文件。可基于动态和静态两种方式实现等待队列的初始化。

静态方式:

 

DECLARE_WAIT_QUEUE_HEAD(wq);

 

其中,wq是要将任务置于睡眠状态的队列的名称。

动态方式:

 

wait_queue_head_t wq;
init_waitqueue_head (&wq);

 

除了创建等待队列的方式不同之外,其他操作对于静态和动态方法都是相同的。

3. 排队

一旦声明并初始化了等待队列,进程就可以使用它进入睡眠状态。有几个宏可用于不同的用途。我们将逐一说明。

wait_event

wait_event_timeout

wait_event_cmd

wait_event_interruptible

wait_event_interruptible_timeout

wait_event_killable

每当我们使用上面的宏时,它会将该任务添加到我们创建的等待队列中。然后它会等待事件。

wait_event

进程进入休眠状态(TASK_UNINTERUPTIBLE),直到条件评估为true。每次唤醒等待队列wq时,都会检查该条件。

 

/* wq – 等待队列
 * condition - 要等待的C表达式的事件 
 */
wait_event(wq, condition);

 

wait_event_timeout

进程进入休眠状态(TASK_UNINTERUPTIBLE),直到条件评估为true或超时。每次唤醒等待队列wq时,都会检查该条件。

如果超时后条件评估为false,则返回0;如果超时后情况评估为true,则返回1;如果超时前情况评估为true,则返回剩余的jiffies(至少1)。

 

/* wq – 等待队列
 * condition - 要等待的C表达式的事件 
 * timeout –  超时时间,单位jiffies 
 */
wait_event_timeout(wq, condition, timeout);

 

wait_event_cmd

进程进入休眠状态(TASK_UNINTERUPTIBLE),直到条件评估为true。每次唤醒等待队列wq时,都会检查该条件。

 

/* wq – 等待队列
 * condition - 要等待的C表达式的事件 
 * cmd1–该命令将在睡眠前执行
 * cmd2–该命令将在睡眠后执行
 */
wait_event_cmd(wq, condition, cmd1, cmd2);

 

wait_event_interruptible

进程进入休眠状态(TASK_INTERRUPTIBLE),直到条件评估为真或接收到信号。每次唤醒等待队列wq时,都会检查该条件。

如果被信号中断,函数将返回-ERESTARTSYS,如果条件评估为true,则返回0。

 

/* wq – 等待队列
 * condition - 要等待的C表达式的事件 
 */
wait_event_interruptible(wq, condition);

 

wait_event_interruptible_timeout

进程进入休眠状态(TASK_INTERRUPTIBLE),直到条件评估为真或接收到信号或超时。每次唤醒等待队列wq时,都会检查该条件。

如果超时后条件评估为false,则返回0;如果超时后情况评估为true,则返回1;如果超时前情况评估为true,则返回剩余的jiffies(至少1);如果被信号中断,则返回-ERESTARTSYS。

 

/* wq – 等待队列
 * condition - 要等待的C表达式的事件 
 * timeout –  超时时间,单位jiffies 
 */
wait_event_interruptible_timeout(wq, condition, timeout);

 

wait_event_killable

进程进入休眠状态(TASK_KILLABLE),直到条件评估为真或收到信号。每次唤醒等待队列wq时,都会检查该条件。

如果被信号中断,函数将返回-ERESTARTSYS,如果条件评估为true,则返回0。

 

/* wq – 等待队列
 * condition - 要等待的C表达式的事件 
 */
wait_event_killable(wq, condition);

 

4. 唤醒排队的任务

当一些任务由于等待队列而处于睡眠模式时,我们可以使用下面的函数来唤醒这些任务。

wake_up

wake_up_all

wake_up_interruptible

wake_up_sync and wake_up_interruptible_sync

通常,调用wake_up会立即触发重新调度,这意味着在wake_up返回之前可能会运行其他进程。“同步”变体使任何唤醒的进程都可以运行,但不会重新调度CPU。这用于避免在已知当前进程进入睡眠状态时重新调度,从而强制重新调度。注意,被唤醒的进程可以立即在不同的处理器上运行,因此不应期望这些函数提供互斥

5. 实践

我们在两个地方发送了一个wake_up。一个来自读取功能,另一个来自驱动退出。

首先创建了一个线程(wait_function)。该线程将始终等待该事件。它会一直睡到接到唤醒事件。当它得到wake_up调用时,它将检查条件。如果条件为1,则唤醒来自读取功能。如果是2,则唤醒来自退出功能。如果wake_up来自读取功能,它将打印读取计数,并再次等待。如果它来自exit函数,那么它将从线程中退出。

静态创建wait queue

 

/***************************************************************************//**
*  file       driver.c
*
*  details    Simple linux driver (Waitqueue Static method)
*
*  author     xxx
*******************************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include                  //kmalloc()
#include               //copy_to/from_user()
#include 
#include                  // Required for the wait queues
#include 
 
 
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 functions **********************/
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) {
                pr_info("Waiting For Event...
");
                wait_event_interruptible(wait_queue_etx, wait_queue_flag != 0 );
                if(wait_queue_flag == 2) {
                        pr_info("Event Came From Exit Function
");
                        return 0;
                }
                pr_info("Event Came From Read Function - %d
", ++read_count);
                wait_queue_flag = 0;
        }
        do_exit(0);
        return 0;
}

/*
** This function will be called when we open the Device file
*/
static int etx_open(struct inode *inode, struct file *file)
{
        pr_info("Device File Opened...!!!
");
        return 0;
}

/*
** This function will be called when we close the Device file
*/
static int etx_release(struct inode *inode, struct file *file)
{
        pr_info("Device File Closed...!!!
");
        return 0;
}

/*
** This function 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)
{
        pr_info("Read Function
");
        wait_queue_flag = 1;
        wake_up_interruptible(&wait_queue_etx);
        return 0;
}

/*
** This function 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)
{
        pr_info("Write function
");
        return len;
}
 
/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
                pr_info("Cannot allocate major number
");
                return -1;
        }
        pr_info("Major = %d Minor = %d 
",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){
            pr_info("Cannot add the device to the system
");
            goto r_class;
        }
 
        /*Creating struct class*/
        if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
            pr_info("Cannot create the struct class
");
            goto r_class;
        }
 
        /*Creating device*/
        if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
            pr_info("Cannot create the Device 1
");
            goto r_device;
        }
 
        //Create the kernel thread with name 'mythread'
        wait_thread = kthread_create(wait_function, NULL, "WaitThread");
        if (wait_thread) {
                pr_info("Thread Created successfully
");
                wake_up_process(wait_thread);
        } else
                pr_info("Thread creation failed
");
 
        pr_info("Device Driver Insert...Done!!!
");
        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);
        pr_info("Device Driver Remove...Done!!!
");
}
 
module_init(etx_driver_init);
module_exit(etx_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
MODULE_DESCRIPTION("Simple linux driver (Waitqueue Static method)");
MODULE_VERSION("1.7");

 

动态创建wait queue

 

/****************************************************************************//**
*  file       driver.c
*
*  details    Simple linux driver (Waitqueue Dynamic method)
*
*  author     xxx
*******************************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include                  //kmalloc()
#include               //copy_to/from_user()
#include 
#include                  // Required for the wait queues
#include 
 
 
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 functions **********************/
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) {
                pr_info("Waiting For Event...
");
                wait_event_interruptible(wait_queue_etx, wait_queue_flag != 0 );
                if(wait_queue_flag == 2) {
                        pr_info("Event Came From Exit Function
");
                        return 0;
                }
                pr_info("Event Came From Read Function - %d
", ++read_count);
                wait_queue_flag = 0;
        }
        return 0;
}
 
/*
** This function will be called when we open the Device file
*/ 
static int etx_open(struct inode *inode, struct file *file)
{
        pr_info("Device File Opened...!!!
");
        return 0;
}

/*
** This function will be called when we close the Device file
*/
static int etx_release(struct inode *inode, struct file *file)
{
        pr_info("Device File Closed...!!!
");
        return 0;
}

/*
** This function 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)
{
        pr_info("Read Function
");
        wait_queue_flag = 1;
        wake_up_interruptible(&wait_queue_etx);
        return 0;
}

/*
** This function 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)
{
        pr_info("Write function
");
        return len;
}

/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
                pr_info("Cannot allocate major number
");
                return -1;
        }
        pr_info("Major = %d Minor = %d 
",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){
            pr_info("Cannot add the device to the system
");
            goto r_class;
        }
 
        /*Creating struct class*/
        if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
            pr_info("Cannot create the struct class
");
            goto r_class;
        }
 
        /*Creating device*/
        if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
            pr_info("Cannot create the Device 1
");
            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) {
                pr_info("Thread Created successfully
");
                wake_up_process(wait_thread);
        } else
                pr_info("Thread creation failed
");
 
        pr_info("Device Driver Insert...Done!!!
");
        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);
        pr_info("Device Driver Remove...Done!!!
");
}
 
module_init(etx_driver_init);
module_exit(etx_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
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

 

编译和测试

使用Makefile(sudo make)构建驱动程序

使用sudo insmod driver.ko加载驱动程序

然后检查dmesg

 

Major = 246 Minor = 0
Thread Created successfully
Device Driver Insert...Done!!!
Waiting For Event...

 

因此,该线程正在等待该事件。现在,我们将通过使用sudo cat/dev/etx_device读取驱动程序来发送事件

现在检查dmesg

 

Device File Opened...!!!
Read Function
Event Came From Read Function - 1
Waiting For Event...
Device File Closed...!!!

 

我们从读取功能发送唤醒,因此它将打印读取计数,然后再次休眠。现在通过sudo rmmod驱动程序从退出功能发送事件

 

Event Came From Exit Function
Device Driver Remove...Done!!!

 

现在条件是2。因此,它将从线程返回并删除驱动程序。

 


  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分