Linux kernel 的 wait queue 機制
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 <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
編譯和測試
-
使用
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。因此,它將從線程返回並刪除驅動程序。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/SvUsaMBbmb3_ah_b45Qm6Q