Linux Device Driver Tutorial Part 14 – Workqueue in Linux Kernel Part 1

This article is a continuation of the Series on Linux Device Driver, and carries on the discussion on character drivers and their implementation.This is the Part 14 of Linux device driver tutorial. In our previous tutorial we have seen the Example of Interrupt through Device Driver Programming. Now we will see one of the Bottomhalf which is Workqueue in Linux Kernel.

Bottom Half

When Interrupt triggers, Interrupt Handler should be execute very quickly and it should not run for more time (it should not perform time-consuming tasks). If we have the interrupt handler which is doing more tasks then we need to divide into two halves.

  1. Top Half
  2. Bottom Half

Top Half is nothing but our interrupt handler. If our interrupt handler is doing less task, then top half is more than enough. No need of bottom half in that situation. But if our we have more work when interrupt hits, then we need bottom half. The bottom half runs in the future, at a more convenient time, with all interrupts enabled. So, The job of bottom halves is to perform any interrupt-related work not performed by the interrupt handler.

There are 4 bottom half mechanisms are available in Linux:

  1. Work-queue
  2. Threaded IRQs
  3. Softirqs
  4. Tasklets

In this tutorial, we will see Workqueue in Linux Kernel.

Workqueue in Linux Kernel

Work queues are added in linux kernel 2.6 version. Work queues are a different form of deferring work. Work queues defer work into a kernel thread; this bottom half always runs in process context. Because, Work queue is allowing users to create a kernel thread and bind work to the kernel thread. So, this will run in process context and  the work queue can sleep.

  • Code deferred to a work queue has all the usual benefits of process context.
  • Most importantly, work queues are schedulable and can therefore sleep.

Normally, it is easy to decide between using work queues and softirqs/tasklets:

  • If the deferred work needs to sleep, work queues are used.
  • If the deferred work need not sleep, softirqs or tasklets are used.

There are two ways to implement Workqueue in Linux kernel.

  1. Using global workqueue
  2. Creating Own workqueue (We will see in next tutorial)

Using Global Workqueue (Global Worker Thread)

In this tutorial we will focus on this method.

In this method no need to create any workqueue or worker thread. So in this method we only need to initialize work. We can initialize the work using two methods.

  • Static method
  • Dynamic method (We will see in next tutorial)

Initialize work using Static Method

The below call creates a workqueue by the name and the function that we are passing in the second argument gets scheduled in the queue.

DECLARE_WORK(name, void (*func)(void *))

Where,

name: The name of the “work_struct” structure that has to be created.
func: The function to be scheduled in this workqueue.

Example

Schedule work to the Workqueue

These below functions used to allocate the work to the queue.

Schedule_work

This function puts a job in the kernel-global workqueue if it was not already queued and leaves it in the same position on the kernel-global workqueue otherwise.

int schedule_work( struct work_struct *work );

where,

work – job to be done

Returns zero if work was already on the kernel-global workqueue and non-zero otherwise.

Scheduled_delayed_work

After waiting for a given time this function puts a job in the kernel-global workqueue.

int scheduled_delayed_work( struct delayed_work *dwork, unsigned long delay );

where,

dwork – job to be done

delay – number of jiffies to wait or 0 for immediate execution

Schedule_work_on

This puts a job on a specific cpu.

int schedule_work_on( int cpu, struct work_struct *work );

where,

cpu– cpu to put the work task on

work– job to be done

Scheduled_delayed_work_on

After waiting for a given time this puts a job in the kernel-global workqueue on the specified CPU.

int scheduled_delayed_work_on(
           int cpu, struct delayed_work *dwork, unsigned long delay );

where,

cpu – cpu to put the work task on

dwork – job to be done

delay – number of jiffies to wait or 0 for immediate execution

Delete work from workqueue

There are also a number of helper functions that you can use to flush or cancel work on work queues. To flush a particular work item and block until the work is complete, you can make a call to flush_work. All work on a given work queue can be completed using a call to . In both cases, the caller blocks until the operation is complete. To flush the kernel-global work queue, call flush_scheduled_work.

int flush_work( struct work_struct *work );
void flush_scheduled_work( void );

Cancel Work from wprkqueue

You can cancel work if it is not already executing in a handler. A call to cancel_work_sync will terminate the work in the queue or block until the callback has finished (if the work is already in progress in the handler). If the work is delayed, you can use a call to cancel_delayed_work_sync.

int cancel_work_sync( struct work_struct *work );
int cancel_delayed_work_sync( struct delayed_work *dwork );

Check workqueue

Finally, you can find out whether a work item is pending (not yet executed by the handler) with a call to work_pending ordelayed_work_pending.

work_pending( work );
delayed_work_pending( work );

Programming

Driver Source Code

I took the source code from previous interrupt example tutorial. In that source code, When we read the /dev/etx_device interrupt will hit (To understand interrupts in Linux go to this tutorial). Whenever interrupt hits, I’m scheduling the work to the workqueue. I’m not going to do any job in both interrupt handler and workqueue function,  since it is a tutorial post. But in real workqueues, this function can be used to carry out any operations that need to be scheduled.

MakeFile

Building and Testing Driver

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod driver.ko
  • To trigger interrupt read device file (sudo cat /dev/etx_device)
  • Now see the Dmesg (dmesg)

[email protected]: dmesg

[11213.943071] Major = 246 Minor = 0
[11213.945181] Device Driver Insert…Done!!!
[11217.255727] Device File Opened…!!!
[11217.255747] Read function
[11217.255783] Shared IRQ: Interrupt Occurred
[11217.255845] Executing Workqueue Function
[11217.255860] Device File Closed…!!!

  • We can able to see the print “Shared IRQ: Interrupt Occurred“ and “Executing Workqueue Function
  • Unload the module using sudo rmmod driver

In our next tutorial we will discuss Workqueue using Dynamic method.

Download our new Android app. You can learn all Embedded Tutorials from your Android Phone easily.

Click Here to Download App!