Why Interrupt Handlers Can't Sleep: Deep Dive into Linux Kernel Sleep and Wakeup
This article explains the purpose and mechanics of process sleep in the Linux kernel, describes how wait queues and wake‑up functions operate, and details why sleeping in interrupt context or while holding spinlocks, seqlocks, or RCU locks leads to deadlocks and system crashes.
Background
In Linux driver development a process may be put to sleep when a system call cannot obtain a required resource. The kernel changes the process state to TASK_UNINTERRUPTIBLE or TASK_INTERRUPTIBLE, removes it from the run‑queue and inserts it into a wait queue. The process is later awakened by another context (process, system‑call implementation or interrupt handler) via wake_up() or related functions.
Purpose of Sleep
Sleep is a synchronization primitive that allows a process to wait for an event or resource without consuming CPU time. While sleeping the process is not scheduled on any CPU.
What Can Be Put to Sleep
Only a struct task_struct (i.e., a process) can be put to sleep. The sleep primitive inserts the current task into a wait_queue_head_t and changes its state.
Key Data Structures
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
typedef struct __wait_queue {
unsigned int flags; /* WQ_FLAG_EXCLUSIVE etc. */
void *private; /* points to task_struct */
wait_queue_func_t func; /* wake function */
struct list_head task_list; /* linked list node */
} wait_queue_t;Sleep Macro (wait_event)
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)Wake‑up Macro and Core Function
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
void __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(__wake_up);The common routine iterates over each wait_queue_t in the list, calls its func (normally autoremove_wake_function), and removes the entry if it was exclusive.
Important Usage Guidelines
Never sleep while holding a spinlock, seqlock, RCU lock, or with interrupts disabled – these are atomic contexts where sleeping can deadlock the system.
Sleeping while holding a semaphore is allowed, but the sleep must be short and must not block the thread that will eventually release the semaphore.
After being woken, a process must re‑check the condition because the resource may have been taken by another task.
Why Sleeping in Interrupt Context Is Forbidden
Interrupt handlers run in atomic context; the current task may be unrelated to the interrupt. Sleeping would block the CPU and can cause deadlocks, especially if interrupts arrive repeatedly.
Demonstration of a Deadlock (spin_lock_sleep driver)
The driver inserts the current task into a wait queue, then calls schedule() while still holding a spinlock. The kernel prints messages such as BUG: scheduling while atomic and a full backtrace, confirming the deadlock.
# insmod spin_lock_sleep.ko
# mknod spin_lock_sleep c 252 0
# cat spin_lock_sleep
spin_lock_sleep_read:prepare to get spin_lock! PID:1227
spin_lock_sleep_read:have got the spin_lock! PID:1227
spin_lock_sleep_read:prepare to sleep! PID:1227
spin_lock_sleep_write:prepare to get spin_lock! PID:1229
BUG: spinlock cpu recursion on CPU#0, sh/1229
... (backtrace omitted for brevity) ...When a second process writes to the device, the write path attempts to acquire the same spinlock while the read path is sleeping, leading to a lock‑up:
# echo 'l' > spin_lock_sleep
BUG: scheduling while atomic: cat/540/0x00000002
... (backtrace) ...Correct Handling of Wait Queues
Typical wake‑up functions:
int autoremove_wake_function(wait_queue_t *wait, unsigned mode,
int sync, void *key)
{
int ret = default_wake_function(wait, mode, sync, key);
if (ret)
list_del_init(&wait->task_list);
return ret;
}
EXPORT_SYMBOL(autoremove_wake_function);
int default_wake_function(wait_queue_t *curr, unsigned mode,
int wake_flags, void *key)
{
return try_to_wake_up(curr->private, mode, wake_flags);
}
EXPORT_SYMBOL(default_wake_function);Summary of Restrictions
Never sleep in atomic contexts (spinlock, seqlock, RCU, IRQ‑disabled).
Only sleep when you are certain another context will wake the task.
Always re‑evaluate the waiting condition after wake‑up.
Illustrative Diagrams
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.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
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.
