Fundamentals 11 min read

Mastering Linux Process Sleep and Wake‑Up: Avoiding Invalid Wake‑Ups

This article explains Linux process states, how the scheduler puts processes to sleep and wakes them, describes the problem of invalid wake‑ups caused by race conditions, and provides kernel‑level coding patterns to prevent such issues while illustrating with concrete code examples.

Open Source Linux
Open Source Linux
Open Source Linux
Mastering Linux Process Sleep and Wake‑Up: Avoiding Invalid Wake‑Ups

Linux Process Sleep and Wake‑Up

In Linux, a process that is only waiting for CPU time is in the TASK_RUNNING state and resides on the run queue. When its time slice expires, the scheduler removes it from the run queue and selects another process to run.

A process can voluntarily give up the CPU by calling the scheduling function schedule(). After being rescheduled, it resumes execution right after the schedule() call.

If a process must wait for an event such as device initialization, I/O completion, or a timer, it is removed from the run queue and placed on a wait queue, entering a sleep state.

Linux defines two sleep states:

Interruptible sleep – state flag TASK_INTERRUPTIBLE Uninterruptible sleep – state flag TASK_UNINTERRUPTIBLE Interruptible sleep ends when a hardware interrupt, resource release, or signal occurs. Uninterruptible sleep is similar but ignores signals; it is used when a process must not be interrupted.

Typical code to put a running process to sleep:

sleeping_task = current;
set_current_state(TASK_INTERRUPTIBLE);
schedule();
func1();
/* Rest of the code ... */

Here current is a macro pointing to the current process structure. set_current_state() changes the state from TASK_RUNNING to TASK_INTERRUPTIBLE. When schedule() is invoked by a TASK_RUNNING process, another process is scheduled.

Invalid Wake‑Up

Sometimes a process checks a condition, finds it true, and then goes to sleep, causing an indefinite block known as an invalid wake‑up. This arises from race conditions when multiple processes manipulate shared data.

Example: Process A checks whether a list is empty and goes to sleep if it is. Process B adds a node to the list and calls wake_up_process(). If B runs after A has checked the condition but before A has set its state to sleep, the wake‑up is ineffective, and A will later sleep forever.

Process A (original)

1  spin_lock(&list_lock);
2  if (list_empty(&list_head)) {
3      spin_unlock(&list_lock);
4      set_current_state(TASK_INTERRUPTIBLE);
5      schedule();
6      spin_lock(&list_lock);
7  }
8  /* Rest of the code ... */
9  spin_unlock(&list_lock);

Process B

100 spin_lock(&list_lock);
101 list_add_tail(&list_head, new_node);
102 spin_unlock(&list_lock);
103 wake_up_process(processa_task);

If B wakes A before A has entered the sleep state, the wake‑up is lost and A will sleep indefinitely.

Avoiding Invalid Wake‑Up

The solution is to make the check‑and‑sleep operation atomic. Set the process state to TASK_INTERRUPTIBLE before testing the condition, and reset it to TASK_RUNNING if the condition is already satisfied.

Revised Process A

1 set_current_state(TASK_INTERRUPTIBLE);
2 spin_lock(&list_lock);
3 if (list_empty(&list_head)) {
4     spin_unlock(&list_lock);
5     schedule();
6     spin_lock(&list_lock);
7 }
8 set_current_state(TASK_RUNNING);
9 /* Rest of the code ... */
10 spin_unlock(&list_lock);

By setting the state first, any concurrent wake_up_process() will correctly transition the process back to TASK_RUNNING, preventing an invalid wake‑up.

Kernel‑Level Example

Linux kernel code follows the same pattern when a thread needs to sleep:

/* q is the wait queue we want to sleep on */
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(q, &wait);
set_current_state(TASK_INTERRUPTIBLE);
/* condition is the wait condition */
while (!condition) {
    schedule();
    set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING);
remove_wait_queue(q, &wait);

This ensures the thread is added to the wait queue before checking the condition, and it is removed only after the condition becomes true.

Another kernel snippet shows a migration thread that sleeps until kthread_should_stop() returns true:

set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
    schedule();
    set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;

In all cases, the process state is set to a sleep state before the condition check, guaranteeing that any wake‑up occurring after the check but before schedule() will be effective, thus eliminating invalid wake‑ups.

Summary

To prevent invalid wake‑ups in Linux, set the process state to TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE before evaluating the wait condition, and reset it to TASK_RUNNING once the condition is satisfied. This atomic approach ensures that wake‑up calls are never lost.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

kernellinuxprocess schedulingsleep statewake up
Open Source Linux
Written by

Open Source Linux

Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.