Why Wait Queues Matter: Mastering Linux Kernel Process Sleep and Wakeup
This article explains the Linux kernel wait‑queue mechanism, detailing its data structures, sleep states, step‑by‑step process‑sleep and wake‑up procedures, core APIs, and a practical key‑driver example that demonstrates efficient blocking I/O without wasting CPU cycles.
1. Understanding Wait Queues
In the Linux kernel, a wait queue is a special list of processes used to implement asynchronous event notification and synchronized access to system resources. When a process must wait for an event such as device data readiness or a shared resource, it is added to a wait queue and put to sleep, releasing the CPU.
The wait‑queue structure consists of a wait_queue_head_t (the queue head) and one or more wait_queue_entry_t items linked together. The head contains a spinlock and a list_head that protects the list, while each entry holds flags, a private pointer to the waiting task, a wake‑up function, and a list node.
Without wait queues, a process would poll in a busy‑wait loop, constantly checking the condition and wasting CPU time. Wait queues avoid this by allowing the process to sleep until the condition becomes true, dramatically improving system resource utilization.
1.1 What Is a Wait Queue?
A wait queue is a kernel‑space collection of processes that are blocked on a specific condition. It is used for device‑ready notifications, semaphore synchronization, and other blocking scenarios.
1.2 Why Use a Wait Queue?
Traditional polling consumes CPU cycles. By inserting the process into a wait queue, the kernel can schedule other runnable tasks, and the sleeping process is awakened only when the awaited event occurs.
1.3 Data‑Structure Details
(1) Wait‑queue head ( wait_queue_head_t ) : contains a spinlock_t lock to protect concurrent access and a struct list_head task_list that links all waiting entries.
(2) Wait‑queue entry ( wait_queue_entry_t ) : holds a flags field (0 for non‑exclusive wake‑up, 1 ( WQ_FLAG_EXCLUSIVE) for exclusive wake‑up to avoid the “thundering herd”), a private pointer to the task’s task_struct, a func pointer to the wake‑up function (normally a wrapper around try_to_wake_up()), and a task_list node for linking into the head.
#include <linux/wait.h>
// Static definition and initialization of a wait‑queue head
DECLARE_WAIT_QUEUE_HEAD(my_wq);
// Dynamic initialization
wait_queue_head_t my_wq2;
init_waitqueue_head(&my_wq2);Entry initialization example (binding the current process and setting exclusive wake‑up):
wait_queue_t wait;
init_waitqueue_entry(&wait, current);
wait.flags |= WQ_FLAG_EXCLUSIVE;2. Process Sleep and Wake‑up Mechanisms
2.1 Reasons for Process Sleep
A process sleeps to wait for an event or resource, such as pending disk I/O or a held semaphore. Sleeping frees the CPU for other tasks and prevents wasteful busy‑waiting.
2.2 Sleep States
Linux defines several sleep states: TASK_INTERRUPTIBLE – shallow sleep; the process can be awakened by signals. TASK_UNINTERRUPTIBLE – deep sleep; signals are ignored until the awaited condition is satisfied. TASK_KILLABLE – intermediate state; only fatal signals (e.g., SIGKILL) can wake the process.
2.3 Steps to Put a Process to Sleep
Define and initialize a wait‑queue head (static DECLARE_WAIT_QUEUE_HEAD(name) or dynamic init_waitqueue_head(&q)).
Create a wait‑queue entry and add it to the head with prepare_to_wait() (or DEFINE_WAIT()).
Set the process state to one of the sleep states ( set_current_state(TASK_INTERRUPTIBLE), etc.).
Re‑check the sleep condition; if it is already true, skip sleeping with finish_wait().
Call schedule() to relinquish the CPU.
After being awakened, remove the entry from the queue with finish_wait() and restore the state to TASK_RUNNING.
Note: Sleeping must occur in process context, never in interrupt context, and a process must not hold spinlocks or other non‑sleepable locks while sleeping.
2.4 Wake‑up Triggers
A process is awakened when the awaited condition becomes true, such as hardware interrupt completion, I/O data readiness, resource release, or a specific signal.
2.5 Wake‑up Functions
The kernel provides several wake‑up helpers: wake_up() – wakes non‑exclusive and one exclusive waiter. wake_up_interruptible() – wakes only TASK_INTERRUPTIBLE waiters. wake_up_all() – wakes every waiter in the queue.
// Wake up an interruptible waiter (most common)
wake_up_interruptible(&key_wq);
// Generic wake‑up (covers most states)
wake_up(&key_wq);
// Wake up all waiters
wake_up_all(&key_wq);3. Core Wait‑Queue API
3.1 Initialization API
Static initialization:
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)Example:
static DECLARE_WAIT_QUEUE_HEAD(key_wq);Dynamic initialization:
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
__init_waitqueue_head((q), #q, &__key); \
} while (0) wait_queue_head_t key_wq;
init_waitqueue_head(&key_wq);3.2 Sleep API
wait_event(wq, condition)adds the current process to wq and puts it into an uninterruptible sleep until condition evaluates to true. If the condition is already true, the process does not sleep. wait_event_interruptible(wq, condition) behaves similarly but uses the interruptible state. It returns 0 on normal wake‑up and -ERESTARTSYS if a signal interrupts the sleep.
int ret;
ret = wait_event_interruptible(key_wq, key_event_flag != 0);
if (ret == -ERESTARTSYS) {
printk("Process interrupted, exiting block
");
return -ERESTARTSYS;
}
/* Continue with key handling */3.3 Wake‑up API
The primary wake‑up macro:
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)Typical usage in an interrupt handler:
irqreturn_t key_irq_handler(int irq, void *dev_id)
{
key_event_flag = 1; // mark event
wake_up(&key_wq); // wake waiting process
return IRQ_HANDLED;
}When using wake‑up functions, ensure the condition variable is set to true before calling the wake‑up routine; otherwise the process may immediately re‑enter sleep.
4. Classic Scenario – Blocking I/O in a Key Driver
4.1 User‑space Read Request
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define KEY_DEV "/dev/key_device"
int main()
{
int fd;
char key_value;
fd = open(KEY_DEV, O_RDONLY);
if (fd < 0) {
perror("open key device failed");
return -1;
}
ssize_t ret = read(fd, &key_value, 1);
if (ret < 0) {
perror("read key value failed");
} else {
printf("Read key value: %c
", key_value);
}
close(fd);
return 0;
}4.2 Blocking When No Key Is Pressed
In the driver’s read method, the process waits on key_wq until key_event_flag becomes non‑zero:
ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
int ret;
ret = wait_event_interruptible(key_wq, key_event_flag != 0);
if (ret == -ERESTARTSYS) {
printk("Process interrupted, exiting block
");
return -ERESTARTSYS;
}
/* Read key value and copy to user space */
return count;
}4.3 Wake‑up on Key Press and Data Retrieval
The interrupt handler sets the flag and wakes the waiting process:
irqreturn_t key_irq_handler(int irq, void *dev_id)
{
key_event_flag = 1; // event occurred
wake_up(&key_wq); // wake the sleeper
return IRQ_HANDLED;
}After wake‑up, the driver reads the key value, copies it to user space, and resets the flag:
ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
int ret;
char key_value;
ret = wait_event_interruptible(key_wq, key_event_flag != 0);
if (ret == -ERESTARTSYS) {
printk("Process interrupted, exiting block
");
return -ERESTARTSYS;
}
key_value = read_key_value();
if (copy_to_user(buf, &key_value, 1))
return -EFAULT;
key_event_flag = 0; // reset for next event
return 1;
}This complete flow demonstrates how wait queues provide an efficient blocking I/O mechanism: the process sleeps without consuming CPU, is promptly awakened when the hardware event occurs, and then safely reads the data.
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.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
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.
