If you’ve been following our series on Interrupt Management in Linux Device Drivers, you’ve already met the Top Half and Bottom Half duo, and dived into SoftIRQs — the backbone of deferred interrupt handling.Now, let’s move to another powerful and simpler mechanism built on top of SoftIRQs — Tasklets.
Previous Articles for reference
- How Linux Handles Interrupts Using Top and Bottom Halves
- Understanding Bottom Halves in Linux Device Drivers
- A Deep Dive into SoftIRQs in Linux Device Drivers
⚙️ What Are Tasklets?
Tasklets are one of the most commonly used bottom-half mechanisms in Linux.
They are built upon the SoftIRQ framework but provide a simpler and easier-to-use interface for driver developers.
Unlike SoftIRQs, which can be shared across multiple subsystems, Tasklets are dynamically created, per-driver or per-device deferred work units.
Think of a tasklet as a function scheduled to run later in a safe, atomic context — not immediately, but soon after the interrupt is handled.
🧠 Key Characteristics of Tasklets
- Run in softirq context
- Always executed on the same CPU where they were scheduled
- Cannot sleep (they run in interrupt context)
- Serialized per tasklet — meaning a tasklet instance never runs concurrently on two CPUs
- Great for short, quick deferred operations
🔍 How Tasklet is Built on SoftIRQs
If you explore the kernel source code (kernel/softirq.c), you’ll find that tasklets are implemented using SoftIRQs internally.
Here’s the relevant snippet (simplified):
// Defined in include/linux/interrupt.h
struct tasklet_struct {
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
The kernel maintains a set of queues for pending tasklets and schedules them via raise_softirq(TASKLET_SOFTIRQ) when needed.
This shows how tasklets rely on the same underlying SoftIRQ mechanism, but offer a higher-level API for developers.
💻 A Simple Example — Using Tasklet in a Driver
Let’s look at a minimal example of scheduling and executing a tasklet:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
void my_tasklet_function(unsigned long data)
{
pr_info("Tasklet executed with data: %lu\n", data);
}
// Declare and initialize a tasklet
DECLARE_TASKLET(my_tasklet, my_tasklet_function, 123);
static int __init tasklet_example_init(void)
{
pr_info("Scheduling Tasklet...\n");
tasklet_schedule(&my_tasklet); // Schedule the tasklet
return 0;
}
static void __exit tasklet_example_exit(void)
{
tasklet_kill(&my_tasklet); // Clean up
pr_info("Tasklet killed.\n");
}
module_init(tasklet_example_init);
module_exit(tasklet_example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embitude");
MODULE_DESCRIPTION("Simple Tasklet Example");
🧩 How this works:
- The tasklet is initialized using
DECLARE_TASKLET(). - When
tasklet_schedule()is called, the kernel raises aTASKLET_SOFTIRQ. - The SoftIRQ mechanism ensures the tasklet function runs shortly after in interrupt context.
⚡ Where Are Tasklets Used in the Kernel?
Tasklets are used in various kernel subsystems where lightweight deferred execution is required.
Some examples include:
- Network drivers (for packet transmission and cleanup)
- Block device drivers
- Timers
- USB and SPI drivers for small deferred operations
Tasklets help drivers handle quick post-interrupt tasks without blocking critical paths, making them a go-to choice for many real-world embedded systems.
🧩 Tasklet vs SoftIRQs — When to Use What
| Feature | SoftIRQ | Tasklet |
|---|---|---|
| Context | SoftIRQ context | SoftIRQ context |
| Concurrency | Can run on multiple CPUs | Serialized per tasklet |
| Customization | Kernel-level subsystems | Per-driver usage |
| Ease of use | Complex | Simple API |
| Sleep allowed? | No | No |