Kernel Module 6 Workqueue - FrankBau/meta-marsboard-bsp GitHub Wiki

A workqueue is used to put so called work items into a queue and execute them a little later in a safe kernel process context.

Typically work items are created in an interrupt handler where there is no process context and only a very limited subset of kernel functions is allowed.

Let's define our own "class" struct my_work_struct based upon struct work_struct and a workqueue. Since we have to use plain "C", subclassing involves some nasty type casting, see below.

#include <linux/workqueue.h>
#include <linux/slab.h>
...
struct my_work_struct {
    struct work_struct work;    // "base class" members, do not touch
    int my_irq_counter;         // my data: number of irq
    unsigned long my_jiffies;   // my data: time of irq
};

// the workqueue
static struct workqueue_struct *my_workqueue;

Let's also define a callback function which will be called by the kernel later when it deques the queued work items:

// called by the kernel to dequeue my work item
static void my_workqueue_function( struct work_struct *work )
{
    struct my_work_struct *my_work = (struct my_work_struct *)work; // downcasting to struct my_work_struct

    pr_info("my_workqueue_function, my_irq_counter=%4d, my_jiffies=%lu\n", my_work->my_irq_counter, my_work->my_jiffies );
  
    kfree(work); // free the memory allocated for my work item

    return;
}

Work items are created in the interrupt handler, filled with my data and enqueued in the workqueue:

static irqreturn_t irq_handler( int irq, void *dev_id )
{
    // allocate memory for my work item
    struct my_work_struct *my_work = (struct my_work_struct *)kmalloc(sizeof(struct my_work_struct), GFP_KERNEL);

    pr_info( " irq_handler at jiffies=%lu\n", jiffies );

    if (!my_work) {
        pr_err("failed to kmalloc my_work");
    }
    else {
        INIT_WORK( (struct work_struct *)my_work, &my_workqueue_function );
        my_work->my_irq_counter = irq_counter;
        my_work->my_jiffies = jiffies;
        queue_work( my_workqueue, (struct work_struct *)my_work ); // enqueue my work item
    }

    irq_counter++;

    return IRQ_HANDLED;
}

Finally, the workqueue must be created in the init function and destroyed in the exit function of the module.

static int __init my_init(void)
{
    ...
    my_workqueue = create_workqueue("my_queue");
    ...
}

static void __exit my_exit(void)
{
    ...
    flush_workqueue( my_workqueue );
    destroy_workqueue( my_workqueue );
    ...
}

The call to flush_workqueue will block until all remaining items in the queue are processed which allows to free all allocated resources. Make sure that no new work items will be enqueued when flush_workqueue is called.

Delayed Work

Sometimes it is desirable to delay the processing of work items by some additional amount of time (jiffies). In order to do such delayed work, replace in the above code:

  • struct work_struct by struct delayed_work
  • INIT_WORK by INIT_DELAYED_WORK
  • queue_work by queue_delayed_work

Alternatives / Related Concepts

Kernel thread deamon kthreadd, see function kthread_create.

Further Reading