Fundamentals 11 min read

Mastering Linux Kernel Workqueues: Theory, Structures, and a Practical Driver Demo

This guide explains Linux kernel workqueues, their core structures and default system queues, shows how to use the schedule_work API, and walks through a complete driver example that captures the ESC key, queues a work item, and verifies execution via dmesg.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Mastering Linux Kernel Workqueues: Theory, Structures, and a Practical Driver Demo

What Is a Workqueue?

A workqueue in the Linux kernel is a mechanism for handling the bottom half of interrupt processing. It works like a message queue, holding work_struct items that are processed by kernel threads.

The diagram illustrates the various mechanisms available for the lower half of interrupt handling and how to choose among them based on business scenarios and constraints.

Key Data Structures

Two structures are central to workqueues:

struct workqueue_struct {
    struct list_head        pwqs;   /* all pwqs of this wq */
    struct list_head        list;   /* all workqueues */
    ...
    char                    name[WQ_NAME_LEN];
    ...
    unsigned int            flags ____cacheline_aligned;
    struct pool_workqueue __percpu *cpu_pwqs;
    struct pool_workqueue __rcu *numa_pwq_tbl[];
};

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;   // pointer to handler
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

Work items are linked together in a list and are taken by kernel threads created either by drivers (via kthread_create) or pre‑allocated by the kernel.

Default System Workqueues

The kernel creates several global workqueues that are shared by all drivers, defined in include/linux/workqueue.h:

extern struct workqueue_struct *system_wq;
extern struct workqueue_struct *system_highpri_wq;
extern struct workqueue_struct *system_long_wq;
extern struct workqueue_struct *system_unbound_wq;
extern struct workqueue_struct *system_freezable_wq;
extern struct workqueue_struct *system_power_efficient_wq;
extern struct workqueue_struct *system_freezable_power_efficient_wq;

These queues are instantiated in kernel/workqueue.c during early kernel initialization:

int __init workqueue_init_early(void)
{
    ...
    system_wq = alloc_workqueue("events", 0, 0);
    system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
    system_long_wq = alloc_workqueue("events_long", 0, 0);
    system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE);
    system_freezable_wq = alloc_workqueue("events_freezable", WQ_FREEZABLE, 0);
    system_power_efficient_wq = alloc_workqueue("events_power_efficient", WQ_POWER_EFFICIENT, 0);
    system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient", WQ_FREEZABLE | WQ_POWER_EFFICIENT, 0);
    ...
}

Because system_wq is heavily used, the kernel provides a convenience wrapper:

static inline bool schedule_work(struct work_struct *work)
{
    return queue_work(system_wq, work);
}

Driver Example Using a Workqueue

The following example creates a simple driver that registers an interrupt handler for a keyboard key (ESC). When the key is pressed, the handler queues a work item on system_wq and the work handler prints a message.

Directory Layout

$ cd tmp/linux-4.15/drivers
$ mkdir my_driver_interrupt_wq
$ touch my_driver_interrupt_wq.c
$ touch Makefile

Full Driver Source (my_driver_interrupt_wq.c)

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>

static int irq;
static char *devname;
static struct work_struct mywork;

module_param(irq, int, 0644);
module_param(devname, charp, 0644);

#define MY_DEV_ID 1226

struct myirq { int devid; };
struct myirq mydev = { MY_DEV_ID };

#define KBD_DATA_REG   0x60
#define KBD_STATUS_REG 0x64
#define KBD_SCANCODE_MASK 0x7f
#define KBD_STATUS_MASK   0x80

static void mywork_handler(struct work_struct *work)
{
    printk("mywork_handler is called. 
");
}

static irqreturn_t myirq_handler(int irq, void *dev)
{
    struct myirq mydev;
    unsigned char key_code;
    mydev = *(struct myirq *)dev;
    if (MY_DEV_ID == mydev.devid) {
        key_code = inb(KBD_DATA_REG);
        if (key_code == 0x01) { // ESC
            printk("ESC key is pressed! 
");
            INIT_WORK(&mywork, mywork_handler);
            schedule_work(&mywork);
        }
    }
    return IRQ_HANDLED;
}

static int __init myirq_init(void)
{
    printk("myirq_init is called. 
");
    if (request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev) != 0) {
        printk("register irq[%d] handler failed. 
", irq);
        return -1;
    }
    printk("register irq[%d] handler success. 
", irq);
    return 0;
}

static void __exit myirq_exit(void)
{
    printk("myirq_exit is called. 
");
    free_irq(irq, &mydev);
}

MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);

Makefile

ifneq ($(KERNELRELEASE),)
    obj-m := my_driver_interrupt_wq.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    clean:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
endif

Build, Load, and Test

$ make
$ sudo insmod my_driver_interrupt_wq.ko irq=1 devname=mydev
$ lsmod | grep my_driver_interrupt_wq
my_driver_interrupt_wq    16384  0
$ dmesg | tail -n 5
[ 188.247636] myirq_init is called.
[ 188.247642] register irq[1] handler success.

Press the ESC key. The kernel captures the keyboard interrupt, invokes myirq_handler, which queues the work item. The workqueue thread then runs mywork_handler and prints:

[ 305.053155] ESC key is pressed!
[ 305.053177] mywork_handler is called.

This confirms that the workqueue mechanism correctly defers work from the interrupt context to a kernel thread.

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.

Cinterrupt()Workqueuedriver
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

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.