This article is a continuation of the Series on Linux Device Drivers and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Tasklet in Linux Device Driver (Static Method) – Linux Device Driver Tutorial Part 20.
Prerequisites
This is the continuation of Interrupts in the Linux Kernel. So I’d suggest you, know some ideas about Linux Interrupts. You can find some useful tutorials about Interrupts and Bottom Halves below.
- Interrupts in Linux Kernel
- Interrupts Example Program
- Workqueue Example – Static Method
- Workqueue Example – Dynamic Method
- Workqueue Example – Own Workqueue
Bottom Half
When Interrupt triggers, Interrupt Handler should be executed very quickly and it should not run for more time (it should not perform time-consuming tasks). If we have the interrupt handler who is doing more tasks then we need to divide it into two halves.
- Top Half
- Bottom Half
The top Half is nothing but our interrupt handler. If we want to do less work, then the top half is more than enough. No need for the bottom half in that situation. But if we have more work when interrupt hits, then we need the bottom half. The bottom half runs in the future, at a more convenient time, with all interrupts enabled. So, The job of the 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:
- Workqueue – Executed in a process context.
- Threaded IRQs
- Softirqs – Executed in an atomic context.
- Tasklet in Linux – Executed in an atomic context.
In this tutorial, we will see Tasklets in Linux Kernel.
Tasklet in Linux Kernel
Tasklets are used to queue up work to be done at a later time. Tasklets can be run in parallel, but the same tasklet cannot be run on multiple CPUs at the same time. Also, each tasklet will run only on the CPU that schedules it, to optimize cache usage. Since the thread that queued up the tasklet must complete before it can run the tasklet, race conditions are naturally avoided. However, this arrangement can be suboptimal, as other potentially idle CPUs cannot be used to run the tasklet. Therefore workqueues can and should be used instead, and workqueues were already discussed here.
In short, a tasklet in linux is something like a very small thread that has neither stack nor context of its own. Such “threads” work quickly and completely.
Points To Remember
Before using Tasklets, you should consider the below points.
- Tasklets are atomic, so we cannot use
sleep()
and such synchronization primitives as mutexes, semaphores, etc. from them. But we can use spinlock. - A tasklet only runs on the same core (CPU) that schedules it.
- Different tasklets can be running in parallel. But at the same time, a tasklet cannot be called concurrently with itself, as it runs on one CPU only.
- Tasklets are executed by the principle of non-preemptive scheduling, one by one, in turn. We can schedule them with two different priorities: normal and high.
We can create a tasklet in Two ways.
- Static Method
- Dynamic Method
In this tutorial, we will see a static method.
Tasklet Structure
This is the important data structure for the tasklet.
struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }; Here,
|
Create Tasklet
The below macros used to create a tasklet.
DECLARE_TASKLET
This macro used to create the tasklet structure and assigns the parameters to that structure. If we are using this macro then the tasklet will be in the enabled state.
|
Example
DECLARE_TASKLET(tasklet,tasklet_fn, 1);
Now we will see how the macro is working. When I call the macro like above, first it creates a tasklet structure with the name of tasklet. Then it assigns the parameter to that structure. It will be looks like below.
struct tasklet_struct tasklet = { NULL, 0, 0, tasklet_fn, 1 }; (or) struct tasklet_struct tasklet; tasklet.next = NULL; taklet.state = TASKLET_STATE_SCHED; //Tasklet state is scheduled tasklet.count = 0; //taskelet enabled tasklet.func = tasklet_fn; //function tasklet.data = 1; //data arg
DECLARE_TASKLET_DISABLED
The tasklet can be declared and set at a disabled state, which means that the tasklet can be scheduled, but will not run until the tasklet is specifically enabled. You need to use tasklet_enable to enable.
|
Enable and Disable Tasklet
tasklet_enable
This used to enable the tasklet.
|
tasklet_disable
This used to disable the tasklet wait for the completion of the tasklet’s operation.
|
tasklet_disable_nosync
This used to disable immediately.
|
NOTE: If the tasklet has been disabled, we can still add it to the queue for scheduling, but it will not be executed on the CPU until it is enabled again. Moreover, if the tasklet has been disabled several times, it should be enabled exactly the same number of times, there is the count field in the structure for this purpose.
Schedule the tasklet
When we schedule the tasklet, then that tasklet is placed into one queue out of two, depending on the priority. Queues are organized as singly-linked lists. At that, each CPU has its own queues.
There are two priorities.
- Normal Priority
- High Priority
tasklet_schedule
Schedule a tasklet with a normal priority. If a tasklet has previously been scheduled (but not yet run), the new schedule will be silently discarded.
|
Example
/*Scheduling Task to Tasklet*/ tasklet_schedule(&tasklet);
tasklet_hi_schedule
Schedule a tasklet with high priority. If a tasklet has previously been scheduled (but not yet run), the new schedule will be silently discarded.
|
tasklet_hi_schedule_first
This version avoids touching any other tasklets. Needed for kmemcheck in order not to take any page faults while enqueueing this tasklet. Consider VERY carefully whether you really need this or tasklet_hi_schedule() .
|
Kill Tasklet
Finally, after a tasklet has been created, it’s possible to delete a tasklet through these below functions.
tasklet_kill
This will wait for its completion and then kill it.
|
Example
/*Kill the Tasklet */ tasklet_kill(&tasklet);
tasklet_kill_immediate
This is used only when a given CPU is in the dead state.
|
Tasklet in Linux – Programming
Driver Source Code
In that source code, When we read the device file ( /dev/etx_device
), the interrupt will hit (To understand interrupts in Linux go to this tutorial). Whenever an interrupt hits, I’m scheduling the task to the tasklet. Since it is a tutorial post, I’m not going to do any job in both interrupt handler and tasklet function. But in a real tasklet, this function can be used to carry out any operations that need to be scheduled.
NOTE: In this source code, many unwanted functions will be there (which is not related to the Tasklet). Because I’m just maintaining the source code throughout these Device driver series.
[Get the source code from GitHub]
/***************************************************************************//** * \file driver.c * * \details Simple linux driver (Tasklet Static method) * * \author EmbeTronicX * * *******************************************************************************/ #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include<linux/slab.h> //kmalloc() #include<linux/uaccess.h> //copy_to/from_user() #include<linux/sysfs.h> #include<linux/kobject.h> #include <linux/interrupt.h> #include <asm/io.h> #include <linux/err.h> #define IRQ_NO 11 void tasklet_fn(unsigned long); /* Init the Tasklet by Static Method */ DECLARE_TASKLET(tasklet,tasklet_fn, 1); /*Tasklet Function*/ void tasklet_fn(unsigned long arg) { printk(KERN_INFO "Executing Tasklet Function : arg = %ld\n", arg); } //Interrupt handler for IRQ 11. static irqreturn_t irq_handler(int irq,void *dev_id) { printk(KERN_INFO "Shared IRQ: Interrupt Occurred"); /*Scheduling Task to Tasklet*/ tasklet_schedule(&tasklet); return IRQ_HANDLED; } volatile int etx_value = 0; dev_t dev = 0; static struct class *dev_class; static struct cdev etx_cdev; struct kobject *kobj_ref; static int __init etx_driver_init(void); static void __exit etx_driver_exit(void); /*************** Driver Functions **********************/ static int etx_open(struct inode *inode, struct file *file); static int etx_release(struct inode *inode, struct file *file); static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off); static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off); /*************** Sysfs Functions **********************/ static ssize_t sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t sysfs_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t count); struct kobj_attribute etx_attr = __ATTR(etx_value, 0660, sysfs_show, sysfs_store); /* ** File operation sturcture */ static struct file_operations fops = { .owner = THIS_MODULE, .read = etx_read, .write = etx_write, .open = etx_open, .release = etx_release, }; /* ** This function will be called when we read the sysfs file */ static ssize_t sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { printk(KERN_INFO "Sysfs - Read!!!\n"); return sprintf(buf, "%d", etx_value); } /* ** This function will be called when we write the sysfsfs file */ static ssize_t sysfs_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t count) { printk(KERN_INFO "Sysfs - Write!!!\n"); sscanf(buf,"%d",&etx_value); return count; } /* ** This function will be called when we open the Device file */ static int etx_open(struct inode *inode, struct file *file) { printk(KERN_INFO "Device File Opened...!!!\n"); return 0; } /* ** This function will be called when we close the Device file */ static int etx_release(struct inode *inode, struct file *file) { printk(KERN_INFO "Device File Closed...!!!\n"); return 0; } /* ** This function will be called when we read the Device file */ static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Read function\n"); asm("int $0x3B"); // Corresponding to irq 11 return 0; } /* ** This function will be called when we write the Device file */ static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Write Function\n"); return len; } /* ** Module Init function */ static int __init etx_driver_init(void) { /*Allocating Major number*/ if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){ printk(KERN_INFO "Cannot allocate major number\n"); return -1; } printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev)); /*Creating cdev structure*/ cdev_init(&etx_cdev,&fops); /*Adding character device to the system*/ if((cdev_add(&etx_cdev,dev,1)) < 0){ printk(KERN_INFO "Cannot add the device to the system\n"); goto r_class; } /*Creating struct class*/ if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){ printk(KERN_INFO "Cannot create the struct class\n"); goto r_class; } /*Creating device*/ if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){ printk(KERN_INFO "Cannot create the Device 1\n"); goto r_device; } /*Creating a directory in /sys/kernel/ */ kobj_ref = kobject_create_and_add("etx_sysfs",kernel_kobj); /*Creating sysfs file for etx_value*/ if(sysfs_create_file(kobj_ref,&etx_attr.attr)){ printk(KERN_INFO"Cannot create sysfs file......\n"); goto r_sysfs; } if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "etx_device", (void *)(irq_handler))) { printk(KERN_INFO "my_device: cannot register IRQ "); goto irq; } printk(KERN_INFO "Device Driver Insert...Done!!!\n"); return 0; irq: free_irq(IRQ_NO,(void *)(irq_handler)); r_sysfs: kobject_put(kobj_ref); sysfs_remove_file(kernel_kobj, &etx_attr.attr); r_device: class_destroy(dev_class); r_class: unregister_chrdev_region(dev,1); cdev_del(&etx_cdev); return -1; } /* ** Module exit function */ static void __exit etx_driver_exit(void) { /*Kill the Tasklet */ tasklet_kill(&tasklet); free_irq(IRQ_NO,(void *)(irq_handler)); kobject_put(kobj_ref); sysfs_remove_file(kernel_kobj, &etx_attr.attr); device_destroy(dev_class,dev); class_destroy(dev_class); cdev_del(&etx_cdev); unregister_chrdev_region(dev, 1); printk(KERN_INFO "Device Driver Remove...Done!!!\n"); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <[email protected]>"); MODULE_DESCRIPTION("A simple device driver - Tasklet Static"); MODULE_VERSION("1.15");
MakeFile
obj-m += driver.o KDIR = /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
Building and Testing Driver
- Build the driver by using Makefile (
sudo make
) - Load the driver using
sudo insmod driver.ko
- To trigger the interrupt read device file (
sudo cat /dev/etx_device
) - Now see the Dmesg (
dmesg
)
inux@embetronicx-VirtualBox: dmesg [ 8592.698763] Major = 246 Minor = 0 [ 8592.703380] Device Driver Insert...Done!!! [ 8601.716673] Device File Opened...!!! [ 8601.716697] Read function [ 8601.716727] Shared IRQ: Interrupt Occurred [ 8601.716732] Executing Tasklet Function : arg = 1 [ 8601.716741] Device File Closed...!!! [ 8603.916741] Device Driver Remove...Done!!!
- We can able to see the print “Shared IRQ: Interrupt Occurred“ and “Executing Tasklet Function: arg = 1“
- Unload the module using
sudo rmmod driver
In our next tutorial, we will discuss the Tasklet using Dynamic Method.
Please find the other Linux device driver tutorials here. You can also implement this tasklet in a real GPIO interrupt device driver.
You can also read the below tutorials.
Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!