Embitude Infotech1

⚙️ Deep Dive into SoftIRQs

In our previous posts, we explored how Linux handles interrupts through Top Halves and Bottom Halves, and how different mechanisms like Tasklets, Workqueues, and Threaded IRQs help balance speed and flexibility. But if we peel back the layers, one mechanism lies at the very heart of this design — the SoftIRQs.

Previous Posts

This post takes you under the hood to understand:

  • What SoftIRQs really are
  • Why they exist
  • Where they’re used in the Linux kernel
  • And how to experiment with them in your own drivers

🧠 What Exactly Are SoftIRQs?

When a hardware interrupt occurs, the kernel must respond fast — that’s handled by the Top Half (ISR).
However, some work still needs to be done after the ISR returns — like processing network packets, scheduling block I/O, or handling timers.

This deferred work is where SoftIRQs come in.

Think of SoftIRQs as the oldest and lowest-level mechanism for deferred execution in Linux.
They were designed for high-performance subsystems where latency and concurrency are critical — such as networking and block devices.

In simple terms:

SoftIRQs = Lightweight, fast, and concurrent bottom halves handled by the kernel itself.


🔍 How the Kernel Defines and Handles SoftIRQs

SoftIRQs are defined and managed inside the kernel source code, mainly in:
📁 kernel/softirq.c
📁 include/linux/interrupt.h

A few important parts of the source:

/* include/linux/interrupt.h */
enum
{
HI_SOFTIRQ = 0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* used by RCU */
NR_SOFTIRQS
};

Each entry represents a SoftIRQ type used by different kernel subsystems.
You’ll notice NET_RX_SOFTIRQ and NET_TX_SOFTIRQ — both are crucial in networking stack processing.

Now, how does the kernel execute them?

When an interrupt schedules a SoftIRQ, the kernel sets a pending flag.
Later, the SoftIRQ daemon (ksoftirqd) or the returning interrupt handler context runs the corresponding function via:

void __do_softirq(void)
{
struct softirq_action *h;
unsigned long pending;

pending = local_softirq_pending();
h = softirq_vec;

do {
if (pending & 1)
h->action(h);
h++;
pending >>= 1;
} while (pending);
}

Each SoftIRQ type has its own handler (h->action) registered using:

open_softirq(softirq_nr, handler);

🌐 Real Kernel Usage: SoftIRQs in Networking

SoftIRQs shine in networking subsystems, where packet processing can’t happen entirely in the interrupt context.

When a network interface receives a packet, the top half (ISR) acknowledges the interrupt, then schedules a NET_RX_SOFTIRQ to process packets later:

napi_schedule_irqoff(napi);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);

Later, the kernel’s SoftIRQ handler (net_rx_action) runs in process context to pull packets from the NIC, push them up the stack, and free buffers.

This division ensures:
✅ Low interrupt latency
✅ Parallel packet handling on multiple CPUs
✅ Controlled deferred processing

Without SoftIRQs, Linux couldn’t sustain the network throughput it does today.


💡 Experimenting with SoftIRQs (Educational Example)

Now, while SoftIRQs are generally reserved for kernel subsystems, you can still explore their behavior conceptually.

Here’s a simplified example that mimics how a SoftIRQ is registered and invoked — great for learning purposes:

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

static void my_softirq_action(struct softirq_action *a)
{
pr_info("SoftIRQ executed on CPU %d\n", smp_processor_id());
}

/* Define a SoftIRQ ID (just for demonstration) */
#define MY_SOFTIRQ 10

static int __init my_softirq_init(void)
{
open_softirq(MY_SOFTIRQ, my_softirq_action);
raise_softirq(MY_SOFTIRQ);
pr_info("SoftIRQ module loaded\n");
return 0;
}

static void __exit my_softirq_exit(void)
{
pr_info("SoftIRQ module unloaded\n");
}

module_init(my_softirq_init);
module_exit(my_softirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embitude");
MODULE_DESCRIPTION("Simple demonstration of SoftIRQ registration and execution");

🧪 Note: You can’t freely define custom SoftIRQs in production code — the kernel reserves them for core subsystems.
This is purely to illustrate how they are registered, raised, and executed.

🧠 Why Learn SoftIRQs?

Even though you may never write your own SoftIRQ, understanding them gives you deep insight into how Linux achieves scalability and low latency at the kernel level.
They’re the reason your Ethernet packets move smoothly from hardware to sockets — and the foundation on which Tasklets were built.


🚀 Take the Next Step: Go Hands-On with Interrupts

If you’ve been following this series, you’ve now seen how interrupts are managed, deferred, and executed inside Linux.

But reading the code is only half the journey — the real understanding comes when you write and debug it yourself.

That’s exactly what we do in the
👉 Project-Oriented Embedded Linux Device Drivers Training

You’ll build, test, and understand kernel mechanisms like SoftIRQs, Tasklets, and Workqueues hands-on.

✅ 30+ hands-on challenges
✅ 21-day challenge-based learning
✅ Real driver project implementation
✅ Lifetime access and mentorship

🔗 Join now: Embedded Linux Device Drivers

🐧 Coming Up Next: Tasklets — SoftIRQs Simplified for Driver Developers

In the next part of this series, we’ll see how Tasklets were built on top of SoftIRQs to make driver-level deferred work simpler, safer, and more intuitive.

Stay tuned — the story of Linux interrupt handling gets even more interesting from here.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top