Linux kernel 的 wait queue 機制

1. 介紹

當編寫 Linux 驅動程序、模塊或內核程序時,一些進程會等待或休眠一些事件。Linux 中有幾種處理睡眠和醒來的方法,每種方法對應不同的需求,而wait queue便是其中一種。

每當進程必須等待一個事件(例如數據的到達或進程的終止)時,它都應該進入睡眠狀態。睡眠會導致進程暫停執行,從而釋放處理器以供其他用途。一段時間後,該過程將被喚醒,並在我們等待的事件到達時繼續其工作。

等待隊列是內核提供的一種機制,用於實現等待。顧名思義,wait queue是等待事件的進程列表。換句話說,當某個條件成立時,等待隊列用於等待有人叫醒你。它們必須小心使用,以確保沒有競爭條件的存在。

實現wait queue的步驟如下:

  1. 初始化等待隊列

  2. 排隊(將任務置於睡眠狀態,直到事件發生)

  3. 喚醒排隊的任務

以下逐步介紹每個步驟的實現方式。

2. 初始化等待隊列

若使用wait queue功能,需要包含/linux/wait.h頭文件。可基於動態和靜態兩種方式實現等待隊列的初始化。

靜態方式:

DECLARE_WAIT_QUEUE_HEAD(wq);

其中,wq 是要將任務置於睡眠狀態的隊列的名稱。

動態方式:

wait_queue_head_t wq;
init_waitqueue_head (&wq);

除了創建等待隊列的方式不同之外,其他操作對於靜態和動態方法都是相同的。

3. 排隊

一旦聲明並初始化了等待隊列,進程就可以使用它進入睡眠狀態。有幾個宏可用於不同的用途。我們將逐一說明。

  1. wait_event

  2. wait_event_timeout

  3. wait_event_cmd

  4. wait_event_interruptible

  5. wait_event_interruptible_timeout

  6. 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. 喚醒排隊的任務

當一些任務由於等待隊列而處於睡眠模式時,我們可以使用下面的函數來喚醒這些任務。

  1. wake_up

  2. wake_up_all

  3. wake_up_interruptible

  4. 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 <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
#include <linux/err.h>
 
 
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...\n");
                wait_event_interruptible(wait_queue_etx, wait_queue_flag != 0 );
                if(wait_queue_flag == 2) {
                        pr_info("Event Came From Exit Function\n");
                        return 0;
                }
                pr_info("Event Came From Read Function - %d\n", ++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...!!!\n");
        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...!!!\n");
        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\n");
        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\n");
        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\n");
                return -1;
        }
        pr_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){
            pr_info("Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
            pr_info("Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
            pr_info("Cannot create the Device 1\n");
            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\n");
                wake_up_process(wait_thread);
        } else
                pr_info("Thread creation failed\n");
 
        pr_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);
        pr_info("Device Driver Remove...Done!!!\n");
}
 
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 <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
#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 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...\n");
                wait_event_interruptible(wait_queue_etx, wait_queue_flag != 0 );
                if(wait_queue_flag == 2) {
                        pr_info("Event Came From Exit Function\n");
                        return 0;
                }
                pr_info("Event Came From Read Function - %d\n", ++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...!!!\n");
        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...!!!\n");
        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\n");
        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\n");
        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\n");
                return -1;
        }
        pr_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){
            pr_info("Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
            pr_info("Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
            pr_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) {
                pr_info("Thread Created successfully\n");
                wake_up_process(wait_thread);
        } else
                pr_info("Thread creation failed\n");
 
        pr_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);
        pr_info("Device Driver Remove...Done!!!\n");
}
 
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

編譯和測試

Major = 246 Minor = 0
Thread Created successfully
Device Driver Insert...Done!!!
Waiting For Event...
Device File Opened...!!!
Read Function
Event Came From Read Function - 1
Waiting For Event...
Device File Closed...!!!
Event Came From Exit Function
Device Driver Remove...Done!!!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/SvUsaMBbmb3_ah_b45Qm6Q