Fundamentals 16 min read

Why Can't Interrupt Handlers Sleep? Understanding Linux Kernel Sleep and Wakeup Mechanisms

This article explains the purpose and mechanics of process sleep in the Linux kernel, detailing how wait queues and wake‑up functions operate, why sleeping in atomic contexts such as interrupt handlers or while holding spinlocks is unsafe, and demonstrates the resulting deadlock with a sample driver.

ITPUB
ITPUB
ITPUB
Why Can't Interrupt Handlers Sleep? Understanding Linux Kernel Sleep and Wakeup Mechanisms

Background and Motivation

The author reflects on a common misunderstanding when learning Linux driver development: why an interrupt routine cannot call sleep functions. Early learning often shows the symptom without explaining the underlying reasons, which this article clarifies.

Purpose of Sleep

Sleep puts a process into a special state ( TASK_UNINTERRUPTIBLE or TASK_INTERRUPTIBLE) so that it stops consuming CPU while waiting for a resource or event. The process is placed on a wait queue and the CPU is given to other runnable tasks. When the awaited condition becomes true, the kernel (or another process) wakes the sleeping process, making sleep a synchronization primitive.

What Sleeps and Who Wakes

Only processes (entities with a task_struct) can be put to sleep. A system call that cannot immediately obtain a resource may invoke sleep, which changes the task state and inserts the process into a wait queue. The sleeping process cannot wake itself; another process or the kernel must call a wake‑up function, typically from another system call implementation or from interrupt handling code.

Wait Queue Data Structures

A wait queue is managed by a wait_queue_head_t structure:

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

Each entry in the queue is a wait_queue_t:

typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);

struct __wait_queue {
    unsigned int flags;
    #define WQ_FLAG_EXCLUSIVE 0x01 /* exclusive wake‑up */
    void *private;               /* points to the sleeping task_struct */
    wait_queue_func_t func;       /* wake‑up routine */
    struct list_head task_list;  /* links into the head's list */
};

The head and its list are illustrated in the following diagram:

Sleep Primitive: 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)

The macro creates a wait_queue_t (via DEFINE_WAIT), inserts it into the queue, checks the condition, and calls schedule() to relinquish the CPU until another entity wakes the process.

Wake‑up Primitive: wake_up

#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);
}

The core function iterates over the list, invoking each entry’s func (usually autoremove_wake_function) which ultimately calls try_to_wake_up to set the task state to TASK_WAKING and make it runnable.

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;
}

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);
}

Important Usage Guidelines

Never sleep while holding a spinlock, seqlock, RCU lock, or when interrupts are disabled; doing so places the code in an atomic context and can deadlock the system.

Sleeping while holding a semaphore is allowed only if the sleep is short and does not block the thread that will later release the semaphore.

After being woken, a process must re‑check the waiting condition because it cannot assume the state of the resource or that no other process has already taken it.

Demonstration of Deadlock When Sleeping in Atomic Context

The author provides a minimal driver spin_lock_sleep that acquires a spinlock and then attempts to sleep, causing a kernel panic on both UP and SMP systems. Sample commands and kernel logs illustrate the lock‑up:

# insmod spin_lock_sleep.ko
# mknod spin_lock_sleep c 252 0
# echo 'l' > spin_lock_sleep

BUG: spinlock cpu recursion on CPU#0, sh/1229
Backtrace:
 ... (stack trace omitted for brevity) ...

When the driver tries to schedule while the spinlock is held, the kernel prints “BUG: scheduling while atomic” and the system hangs, confirming that sleeping in atomic contexts is unsafe.

Conclusion

Sleep in the Linux kernel is a powerful synchronization tool, but it must be used only when the code is not in an atomic context. Proper use of wait queues, wait_event, and wake_up ensures that processes yield the CPU safely and are reliably awakened by other kernel components.

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.

Linux kernelSpinlocksleepwait queueinterrupt contextwake_up
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

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.