Work Queues

Work queues allow you to defer work to be executed later in process context. Unlike tasklets and softirqs, work queue handlers can sleep, making them ideal for I/O operations and complex processing.

Deferred Work Options

Mechanism Context Can Sleep? Use Case
Workqueue Process Yes Heavy processing, I/O
Tasklet Softirq No Quick, lock-free work
Timer Softirq No Time-delayed execution
Threaded IRQ Process Yes Bottom half of interrupt

Basic Work Queue Usage

Using System Work Queue

#include <linux/workqueue.h>

/* Define work structure */
struct my_work_data {
    struct work_struct work;
    int value;
    char buffer[256];
};

/* Work handler */
static void my_work_handler(struct work_struct *work)
{
    struct my_work_data *data = container_of(work,
                                             struct my_work_data, work);

    pr_info("Work executing: value=%d\n", data->value);

    /* Can sleep here! */
    msleep(100);

    /* Can do I/O */
    /* ... */
}

/* Initialize and schedule */
static struct my_work_data my_work;

void start_work(int val)
{
    my_work.value = val;
    INIT_WORK(&my_work.work, my_work_handler);
    schedule_work(&my_work.work);  /* Queue on system workqueue */
}

Static Initialization

static void my_handler(struct work_struct *work);

/* Static work item */
static DECLARE_WORK(my_work, my_handler);

/* Schedule it */
schedule_work(&my_work);

Delayed Work

Execute work after a delay:

#include <linux/workqueue.h>

struct delayed_work my_delayed_work;

static void delayed_handler(struct work_struct *work)
{
    struct delayed_work *dw = container_of(work,
                                           struct delayed_work, work);
    pr_info("Delayed work executing\n");
}

void start_delayed_work(void)
{
    INIT_DELAYED_WORK(&my_delayed_work, delayed_handler);

    /* Execute after 2 seconds */
    schedule_delayed_work(&my_delayed_work, 2 * HZ);
}

void cancel_work(void)
{
    cancel_delayed_work_sync(&my_delayed_work);
}

Creating Custom Work Queue

For high-priority or isolated work:

#include <linux/workqueue.h>

static struct workqueue_struct *my_wq;

static int __init my_init(void)
{
    /* Create dedicated workqueue */
    my_wq = create_singlethread_workqueue("my_worker");
    if (!my_wq)
        return -ENOMEM;

    return 0;
}

static void __exit my_exit(void)
{
    destroy_workqueue(my_wq);
}

void queue_my_work(struct work_struct *work)
{
    queue_work(my_wq, work);
}

Work Queue Creation Options

/* Single-threaded */
wq = create_singlethread_workqueue("my_wq");

/* Multi-threaded (one per CPU) */
wq = create_workqueue("my_wq");

/* With flags */
wq = alloc_workqueue("my_wq",
                     WQ_HIGHPRI | WQ_MEM_RECLAIM,
                     max_active);

Work Queue Flags

Flag Description
WQ_HIGHPRI High priority workers
WQ_MEM_RECLAIM Can be used during memory reclaim
WQ_CPU_INTENSIVE May run long, don’t block other work
WQ_FREEZABLE Participates in system freeze
WQ_UNBOUND Not bound to specific CPU

Complete Example: Deferred I/O

#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/slab.h>

struct io_request {
    struct work_struct work;
    void *buffer;
    size_t size;
    loff_t offset;
    void (*callback)(struct io_request *, int result);
};

static struct workqueue_struct *io_wq;

static void io_work_handler(struct work_struct *work)
{
    struct io_request *req = container_of(work, struct io_request, work);
    int result;

    pr_info("Processing I/O request: offset=%lld, size=%zu\n",
            req->offset, req->size);

    /* Simulate I/O (can sleep!) */
    msleep(50);
    result = 0;  /* Success */

    /* Call completion callback */
    if (req->callback)
        req->callback(req, result);

    kfree(req);
}

int submit_io_request(void *buffer, size_t size, loff_t offset,
                      void (*callback)(struct io_request *, int))
{
    struct io_request *req;

    req = kmalloc(sizeof(*req), GFP_KERNEL);
    if (!req)
        return -ENOMEM;

    req->buffer = buffer;
    req->size = size;
    req->offset = offset;
    req->callback = callback;

    INIT_WORK(&req->work, io_work_handler);
    queue_work(io_wq, &req->work);

    return 0;  /* Request queued */
}

static int __init io_init(void)
{
    io_wq = alloc_workqueue("io_worker", WQ_MEM_RECLAIM, 4);
    if (!io_wq)
        return -ENOMEM;

    return 0;
}

static void __exit io_exit(void)
{
    destroy_workqueue(io_wq);
}

module_init(io_init);
module_exit(io_exit);
MODULE_LICENSE("GPL");

Work Queue from Interrupt

struct my_device {
    struct work_struct work;
    spinlock_t lock;
    int pending_data;
};

/* Interrupt handler - queue work */
static irqreturn_t my_irq_handler(int irq, void *data)
{
    struct my_device *dev = data;

    spin_lock(&dev->lock);
    dev->pending_data = read_hardware();
    spin_unlock(&dev->lock);

    /* Queue work for processing */
    schedule_work(&dev->work);

    return IRQ_HANDLED;
}

/* Work handler - process data */
static void process_work(struct work_struct *work)
{
    struct my_device *dev = container_of(work, struct my_device, work);
    int data;

    spin_lock(&dev->lock);
    data = dev->pending_data;
    spin_unlock(&dev->lock);

    /* Heavy processing in process context */
    pr_info("Processing: %d\n", data);
    /* ... can sleep here ... */
}

static int __init my_init(void)
{
    INIT_WORK(&my_dev.work, process_work);
    spin_lock_init(&my_dev.lock);
    /* ... request IRQ ... */
    return 0;
}

Canceling Work

/* Cancel and wait for completion */
cancel_work_sync(&work);

/* Cancel delayed work and wait */
cancel_delayed_work_sync(&delayed_work);

/* Cancel without waiting */
cancel_work(&work);
cancel_delayed_work(&delayed_work);

/* Flush all pending work on queue */
flush_workqueue(wq);

/* Flush system workqueue */
flush_scheduled_work();

Recurring Work

static struct delayed_work periodic_work;

static void periodic_handler(struct work_struct *work)
{
    pr_info("Periodic work executing\n");

    /* Do periodic task */
    /* ... */

    /* Reschedule ourselves */
    schedule_delayed_work(&periodic_work, HZ);  /* Every second */
}

void start_periodic(void)
{
    INIT_DELAYED_WORK(&periodic_work, periodic_handler);
    schedule_delayed_work(&periodic_work, HZ);
}

void stop_periodic(void)
{
    cancel_delayed_work_sync(&periodic_work);
}

Work Queue Flow

sequenceDiagram
    participant IRQ as Interrupt
    participant WQ as Work Queue
    participant Worker as Worker Thread

    IRQ->>WQ: schedule_work()
    Note over WQ: Work queued
    WQ->>Worker: Wake up
    Worker->>Worker: Execute handler
    Note over Worker: Can sleep here!
    Worker->>Worker: Done

Per-CPU Work

/* Schedule on specific CPU */
schedule_work_on(cpu, &work);

/* Queue on specific CPU */
queue_work_on(cpu, wq, &work);

Best Practices

Do

/* DO: Free resources in work handler */
static void cleanup_work(struct work_struct *work)
{
    struct my_data *data = container_of(work, struct my_data, work);
    /* ... process ... */
    kfree(data);
}

/* DO: Check if work is pending before freeing */
cancel_work_sync(&data->work);
kfree(data);

Don’t

/* DON'T: Use uninitialized work */
schedule_work(&work);  /* Crash! Not initialized */

/* DON'T: Free while work is running */
kfree(data);  /* Work might still be using it! */
schedule_work(&data->work);

Summary

  • Work queues run in process context - can sleep
  • Use system workqueue for simple cases: schedule_work()
  • Create custom workqueues for high-priority or isolated work
  • Use delayed_work for time-deferred execution
  • Always cancel work before freeing resources
  • Use cancel_work_sync() to wait for running work

Next

Learn about lockdep for deadlock detection.


Back to top

Linux Driver Development Guide is a community resource for learning kernel driver development. Not affiliated with the Linux Foundation. Content provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.