Mastering Linux Wait Queues: Step‑by‑Step Driver Implementation Guide
This article explains how Linux kernel drivers use wait queues for process sleep and wake‑up, covering static and dynamic initialization, the various wait_event macros, wake‑up functions, complete driver source code, Makefile setup, and step‑by‑step build and testing instructions.
Introduction
In Linux kernel development a process often needs to sleep until an event occurs (e.g., data arrival or process termination). A wait queue provides a list of tasks that are blocked until a user‑defined condition becomes true, then wakes them safely.
Wait‑queue basics
A wait queue is represented by wait_queue_head_t. Tasks are added to the queue by the kernel macros and are removed when the condition is satisfied or a timeout/signal occurs. Proper use prevents race conditions.
Initializing a wait queue
Include <linux/wait.h>. Two ways to create a queue:
/* Static declaration – global queue */
DECLARE_WAIT_QUEUE_HEAD(wq);
/* Dynamic allocation */
wait_queue_head_t wq;
init_waitqueue_head(&wq);Queueing macros
The kernel offers several macros for sleeping on a wait queue. All take the queue variable wq and a C expression condition. Some also accept a timeout (in jiffies) or pre‑/post‑sleep commands.
wait_event – sleep until condition is true.
wait_event_timeout – sleep until condition is true or the timeout expires.
wait_event_cmd – execute cmd1 before sleeping and cmd2 after waking.
wait_event_interruptible – sleep in TASK_INTERRUPTIBLE state; returns -ERESTARTSYS if a signal interrupts.
wait_event_interruptible_timeout – combines interruptible sleep with a timeout.
wait_event_killable – sleep in TASK_KILLABLE state; also returns -ERESTARTSYS on signal.
Macro prototypes
/* wait_event */
wait_event(wq, condition);
/* wait_event_timeout */
wait_event_timeout(wq, condition, timeout);
/* Returns 0 on timeout, 1 if condition became true after timeout,
or remaining jiffies (>=1) if it became true before timeout. */
/* wait_event_cmd */
wait_event_cmd(wq, condition, cmd1, cmd2);
/* wait_event_interruptible */
wait_event_interruptible(wq, condition); /* returns -ERESTARTSYS on signal */
/* wait_event_interruptible_timeout */
wait_event_interruptible_timeout(wq, condition, timeout);
/* wait_event_killable */
wait_event_killable(wq, condition);Waking up tasks
After the condition is satisfied the driver calls one of the wake‑up helpers:
wake_up(&wq) – wakes one task and forces a reschedule.
wake_up_all(&wq) – wakes all tasks.
wake_up_interruptible(&wq) – wakes tasks sleeping in interruptible state.
wake_up_sync(&wq) and wake_up_interruptible_sync(&wq) – wake tasks without an immediate reschedule, useful when the current task is about to sleep.
Practical example – character driver using wait queues
Common driver skeleton
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
static dev_t dev;
static struct class *dev_class;
static struct cdev my_cdev;
static struct task_struct *wait_thread;
static int wait_queue_flag = 0; /* 0 – idle, 1 – read event, 2 – exit */
static uint32_t read_count = 0;
/* Wait queue – static version */
DECLARE_WAIT_QUEUE_HEAD(wait_queue_etx);
/* For dynamic version replace the line above with:
wait_queue_head_t wait_queue_etx;
init_waitqueue_head(&wait_queue_etx);
*/
static int wait_function(void *data)
{
while (true) {
pr_info("Waiting for event...
");
wait_event_interruptible(wait_queue_etx, wait_queue_flag != 0);
if (wait_queue_flag == 2) {
pr_info("Event from exit function
");
return 0; /* thread terminates */
}
pr_info("Event from read function – %d
", ++read_count);
wait_queue_flag = 0; /* reset */
}
return 0;
}
static int my_open(struct inode *inode, struct file *file)
{ pr_info("Device opened
"); return 0; }
static int my_release(struct inode *inode, struct file *file)
{ pr_info("Device closed
"); return 0; }
static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
pr_info("Read function
");
wait_queue_flag = 1; /* indicate read event */
wake_up_interruptible(&wait_queue_etx);
return 0;
}
static ssize_t my_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{ pr_info("Write function
"); return len; }
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};
static int __init driver_init(void)
{
if (alloc_chrdev_region(&dev, 0, 1, "etx_dev") < 0) {
pr_err("Failed to allocate major number
");
return -1;
}
pr_info("Major=%d Minor=%d
", MAJOR(dev), MINOR(dev));
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
if (cdev_add(&my_cdev, dev, 1) < 0) {
pr_err("cdev_add failed
");
goto err_unregister;
}
dev_class = class_create(THIS_MODULE, "etx_class");
if (IS_ERR(dev_class)) {
pr_err("class_create failed
");
goto err_cdev;
}
if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "etx_device"))) {
pr_err("device_create failed
");
goto err_class;
}
/* Create kernel thread that will wait on the queue */
wait_thread = kthread_create(wait_function, NULL, "WaitThread");
if (IS_ERR(wait_thread)) {
pr_err("kthread_create failed
");
goto err_device;
}
wake_up_process(wait_thread);
pr_info("Driver inserted
");
return 0;
err_device: device_destroy(dev_class, dev);
err_class: class_destroy(dev_class);
err_cdev: cdev_del(&my_cdev);
err_unregister: unregister_chrdev_region(dev, 1);
return -1;
}
static void __exit driver_exit(void)
{
wait_queue_flag = 2; /* tell thread to exit */
wake_up_interruptible(&wait_queue_etx);
device_destroy(dev_class, dev);
class_destroy(dev_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
pr_info("Driver removed
");
}
module_init(driver_init);
module_exit(driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author");
MODULE_DESCRIPTION("Simple character driver demonstrating static and dynamic wait‑queue usage");
MODULE_VERSION("1.0");Dynamic wait‑queue variant
Replace the static declaration with a dynamic one and call init_waitqueue_head(&wait_queue_etx); in driver_init(). The rest of the code stays unchanged.
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) cleanBuild and test procedure
Compile the module: sudo make.
Insert the module: sudo insmod driver.ko. Verify with dmesg – you should see the major/minor numbers and the thread start message.
Read the device to trigger the event: sudo cat /dev/etx_device. The read function sets wait_queue_flag = 1 and calls wake_up_interruptible(). The kernel thread prints the read count and goes back to sleep.
Remove the module: sudo rmmod driver. The exit routine sets wait_queue_flag = 2, wakes the thread, and the thread terminates cleanly.
Sample dmesg output
Major = 246 Minor = 0
Thread Created successfully
Device Driver Insert...Done!!!
Waiting for event...
Device File Opened...
Read Function
Event from read function - 1
Waiting for event...
Device File Closed...
Event from exit function
Device Driver Remove...Done!!!Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
