Softirqs

The term softirq loosely translates to soft interrupt, and as the name suggests, deferred routines managed by this framework are executed at a high priority but with hard interrupt lines enabled. Thus, softirq bottom halves (or softirqs) can preempt all other tasks except hard interrupt handlers. However, usage of softirqs is restricted to static kernel code and this mechanism is not available for dynamic kernel modules.

Each softirq is represented through an instance of type struct softirq_action declared in the kernel header <linux/interrupt.h>. This structure contains a function pointer that can hold the address of the bottom half routine:

struct softirq_action
{
void (*action)(struct softirq_action *);
};

Current versions of the kernel have 10 softirqs, each indexed through an enum in the kernel header <linux/interrupt.h>. These indexes serve as an identity and are treated as the relative priority of the softirq, and entries with lower indexes are considered higher in priority, with index 0 being the highest priority softirq:

enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
numbering. Sigh! */
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS
};

The kernel source file <kernel/softirq.c> declares an array called softirq_vec of size NR_SOFTIRQS, with each offset containing a softirq_action instance of the corresponding softirq indexed in the enum:

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

/* string constants for naming each softirq */
const char * const softirq_to_name[NR_SOFTIRQS] = {
"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL",
"TASKLET", "SCHED", "HRTIMER", "RCU"
};

Framework provides a function open_softriq() used for initializing the softirq instance with the corresponding bottom-half routine:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}

nr is the index of the softirq to be initialized and *action is a function pointer to be initialized with the address of the bottom-half routine. The following code excerpt is taken from the timer service, and shows the invocation of open_softirq to register a softirq:

/*kernel/time/timer.c*/
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);

Kernel services can signal the execution of softirq handlers using a function raise_softirq(). This function takes the index of the softirq as an argument:

void raise_softirq(unsigned int nr)
{
unsigned long flags;

local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

The following code excerpt is from <kernel/time/timer.c>:

void run_local_timers(void)
{
struct timer_base *base = this_cpu_ptr(&amp;timer_bases[BASE_STD]);

hrtimer_run_queues();
/* Raise the softirq only if required. */
if (time_before(jiffies, base->clk)) {
if (!IS_ENABLED(CONFIG_NO_HZ_COMMON) || !base->nohz_active)
return;
/* CPU is awake, so check the deferrable base. */
base++;
if (time_before(jiffies, base->clk))
return;
}
raise_softirq(TIMER_SOFTIRQ);
}

The kernel maintains a per-CPU bitmask for keeping track of softirqs raised for execution, and the function raise_softirq() sets the corresponding bit (index mentioned as argument) in the local CPUs softirq bitmask to mark the specified softirq as pending.

Pending softirq handlers are checked and executed at various points in the kernel code. Principally, they are executed in the interrupt context, immediately after the completion of hard interrupt handlers with IRQ lines enabled. This guarantees swift processing of softirqs raised from hard interrupt handlers, resulting in optimal cache usage. However, the kernel allows an arbitrary task to suspend execution of softirq processing on a local processor either through local_bh_disable() or spin_lock_bh() calls. Pending softirq handlers are executed in the context of an arbitrary task that re-enables softirq processing by invoking either local_bh_enable() or spin_unlock_bh() calls. And lastly, softirq handlers can also be executed by a per-CPU kernel thread ksoftirqd, which is woken up when a softirq is raised by any process-context kernel routine. This thread is also woken up from the interrupt context when too many softirqs accumulate due to high load.

Softirqs are most suitable for completion of priority work deferred from hard interrupt handlers since they run immediately on completion of hard interrupt handlers. However, softirqs handlers are reentrant, and must be programmed to engage appropriate protection mechanisms while accessing data structures, if any. The reentrant nature of softirqs may cause unbounded latencies, impacting the efficiency of the system as a whole, which is why their usage is restricted, and new ones are almost never added, unless it is absolute necessity for the execution of high-frequency threaded deferred work. For all other types of deferred work, tasklets and work queues are suggested.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.13.173