Skip to content

Workqueue and Deferred Execution

Overview

Linux drivers frequently need to perform work that cannot be executed directly inside interrupt handlers.

Examples include:

  • I2C transactions
  • SPI transfers
  • Mutex operations
  • Memory allocation with sleepable flags
  • Long-running recovery procedures

To support these operations safely, Linux provides deferred execution mechanisms.


Why Deferred Execution Exists

Hard IRQ handlers execute in atomic context.

Restrictions:

  • Cannot sleep
  • Cannot schedule
  • Cannot wait for events
  • Cannot acquire mutexes

Therefore, interrupt handlers should perform only minimal processing:

Read status
Clear interrupt
Schedule follow-up work
Return immediately

More complex operations must be moved into a sleepable context.


Linux Execution Contexts

Hard IRQ Context

Examples:

irq_handler_t

Characteristics:

  • Atomic context
  • Cannot sleep
  • Lowest interrupt latency

Suitable for:

  • Interrupt acknowledgement
  • Status collection
  • Event notification

Threaded IRQ Context

Examples:

request_threaded_irq()

Characteristics:

  • Sleepable
  • Dedicated IRQ thread
  • Interrupt-driven processing

Suitable for:

  • Sensor event processing
  • I2C/SPI register access
  • Immediate post-interrupt handling

Workqueue Context

Examples:

schedule_work()
queue_work()

Characteristics:

  • Sleepable
  • Executed by kworker threads
  • Event-driven background processing

Suitable for:

  • Retry operations
  • Deferred processing
  • Recovery procedures
  • Delayed execution

Kernel Thread Context

Examples:

kthread_run()

Characteristics:

  • Sleepable
  • Long-lived execution
  • Driver-managed lifecycle

Suitable for:

  • State machines
  • Monitoring services
  • Connection managers
  • Reclaimers

Workqueue Architecture

Basic workflow:

Event
schedule_work()
Workqueue
kworker
Work Handler

The work handler executes in process context.

Allowed operations:

  • mutex_lock()
  • msleep()
  • wait_event()
  • i2c_transfer()
  • spi_sync()

Global vs Private Workqueues

system_wq

Default global workqueue provided by Linux.

Used by:

schedule_work()

Equivalent to:

queue_work(system_wq, &work);

Advantages:

  • Simple
  • No lifecycle management required

Disadvantages:

  • Shared with other kernel subsystems
  • Shared worker resources

Private Workqueue

Created by:

alloc_workqueue(...)

Advantages:

  • Workload isolation
  • Dedicated worker pool
  • Independent lifecycle

Suitable when:

  • Work execution may be lengthy
  • Driver requires concurrency control
  • Driver requires dedicated resources

Delayed Work

Delayed Work combines:

Timer
+
Workqueue

Execution flow:

schedule_delayed_work()
Timer expires
Workqueue
kworker
Handler

Common applications:

  • Debounce
  • Retry logic
  • Monitoring
  • Timeout handling

Workqueue Lifecycle

Typical driver lifecycle:

Probe
alloc_workqueue()
queue_work()
queue_work()
queue_work()
Destroy

Cleanup sequence:

disable_irq()
cancel_work_sync()
cancel_delayed_work_sync()
destroy_workqueue()
free resources

Purpose:

Prevent use-after-free caused by
pending or running work handlers.

Workqueue vs Threaded IRQ

Threaded IRQ:

IRQ
Immediate Processing
Return

Workqueue:

Event
Background Job
Completion

Selection guideline:

Interrupt-driven processing
    → Threaded IRQ

Background processing
    → Workqueue

Workqueue vs Kernel Thread

Workqueue:

Job-Oriented

Characteristics:

  • Event-driven
  • Finite execution
  • Managed by kernel worker pools

Examples:

  • Recovery work
  • Retry work
  • Debounce work

Kernel Thread:

Service-Oriented

Characteristics:

  • Long-lived execution
  • Continuous operation
  • Explicit lifecycle management

Examples:

  • State machines
  • Monitoring services
  • Protocol engines
  • Reclaimers

Design Selection Guide

Need lowest interrupt latency?
    → Hard IRQ

Need interrupt-driven processing with sleep?
    → Threaded IRQ

Need deferred background work?
    → Workqueue

Need long-lived service or state machine?
    → Kernel Thread

Key Takeaways

  • Deferred execution moves work into a sleepable context.
  • Workqueues execute jobs using kernel-managed worker threads.
  • Delayed Work combines timer functionality with workqueues.
  • Private workqueues isolate driver workloads from system workqueues.
  • Threaded IRQ is optimized for interrupt-driven processing.
  • Kernel Threads are optimized for long-lived services and state machines.
  • Choosing the correct execution context simplifies driver design and improves maintainability.