[RFC 1/6] softirq: Add softirq_groups boot parameter

Dmitry Safonov
2018-01-18T16:14:19Z

ksoftirqd thread allows to defer softirqs if the system is under storm.
While it prevents userspace from cpu-time starving, it increases
latencies for other softirqs (that are not raised under storm).

As creation of one ksoftirqd thread per-each-softirq-per-cpu will be
insane on a huge machines, separate softirqs by groups.
It will allow to defer softirqs of one group and continue servicing
from other. That means that under a storm of one group's softirqs,
softirqs from the other group will be serviced as they come and will
not have latency issues.
For each softirq group will be created a per-cpu kthread which
will process deferred softirqs of the group.

The parameter will allow an admin define how many ksoftirqd threads
will be created on each cpu and which softirqs have the same
deferring group.

Signed-off-by: Dmitry Safonov <dima@arista.com>
---
Documentation/admin-guide/kernel-parameters.txt | 16 +++++
include/linux/interrupt.h | 1 +
kernel/softirq.c | 87 +++++++++++++++++++++++++
3 files changed, 104 insertions(+)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 46b26bfee27b..d5c44703a299 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -3940,6 +3940,22 @@
Format: <integer>
Default: -1 (no limit)

+ softirq_groups=
+ [KNL] The count and contents of softirq groups.
+ Format:[group1],[group2],[groupN]
+ where group is <softirq1>/<softirq2>/<softirqM>
+ E.g: softirq_groups=HI/TIMER/HRTIMER,NET_TX/NET_RX,BLOCK
+
+ Defines how many ksoftirqd threads create *per-cpu*.
+ For each group one ksoftirqd thread is created.
+ The total number of threads created is
+ (NR_CPUS * NR_SOFTIRQ_GROUPS).
+ Admin can define one softirq in different softirq
+ groups. Softirqs those have no group defined will
+ be put in default softirq_group. If all softirqs
+ have been placed into groups default group is not
+ created.
+
softlockup_panic=
[KNL] Should the soft-lockup detector generate panics.
Format: <integer>
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
index 69c238210325..5bb6b435f0bb 100644
--- a/include/linux/interrupt.h
+++ b/include/linux/interrupt.h
@@ -486,6 +486,7 @@ extern const char * const softirq_to_name[NR_SOFTIRQS];
struct softirq_action
{
void (*action)(struct softirq_action *);
+ u32 group_mask;
};

asmlinkage void do_softirq(void);
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 2f5e87f1bae2..c9aecdd57107 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -54,6 +54,7 @@ EXPORT_SYMBOL(irq_stat);
#endif

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
+static unsigned __initdata nr_softirq_groups = 0;

DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

@@ -635,10 +636,25 @@ void tasklet_hrtimer_init(struct tasklet_hrtimer *ttimer,
}
EXPORT_SYMBOL_GPL(tasklet_hrtimer_init);

+static void __init setup_default_softirq_group(unsigned nr)
+{
+ unsigned i;
+
+ for (i = 0; i < NR_SOFTIRQS; i++) {
+ u32 *gr_mask = &softirq_vec[i].group_mask;
+
+ if (!*gr_mask)
+ *gr_mask |= (1 << nr);
+ pr_debug("softirq-%s: %#x\n", softirq_to_name[i], *gr_mask);
+ }
+}
+
void __init softirq_init(void)
{
int cpu;

+ setup_default_softirq_group(nr_softirq_groups++);
+
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
@@ -750,6 +766,77 @@ static __init int spawn_ksoftirqd(void)
}
early_initcall(spawn_ksoftirqd);

+static __init __u32 parse_softirq_name(char *name, size_t len)
+{
+ __u32 i;
+
+ for (i = 0; i < NR_SOFTIRQS; i++)
+ if (strncmp(name, softirq_to_name[i], len) == 0)
+ return i;
+
+ pr_warn("softirq: Ignored `%.*s' in softirq group", (int)len, name);
+
+ return NR_SOFTIRQS;
+}
+
+static bool __init parse_softirq_group(char *start, char *end, u32 group)
+{
+ char *next_softirq = strchrnul(start, '/');
+ bool is_empty = true;
+ u32 softirq_nr;
+
+ if (next_softirq == start)
+ return !is_empty;
+
+ do {
+ next_softirq = min(next_softirq, end);
+
+ softirq_nr = parse_softirq_name(start, next_softirq - start);
+ if (softirq_nr < NR_SOFTIRQS) {
+ softirq_vec[softirq_nr].group_mask |= (1 << group);
+ is_empty = false;
+ }
+
+ if (next_softirq == end)
+ break;
+
+ start = next_softirq + 1;
+ next_softirq = strchrnul(start, '/');
+ } while (1);
+
+ return !is_empty;
+}
+
+/*
+ * Format e.g.:
+ * softirq_groups=HI/TIMER/HRTIMER,NET_TX/NET_RX,BLOCK,TASKLET
+ * Admin *can* define one softirq in different groups.
+ * Softirqs those have no group defined will be put in default softirq_group.
+ * If all softirqs have been placed into groups, default group is not created.
+ */
+static int __init setup_softirq_groups(char *s)
+{
+ char *next_group = strchrnul(s, ',');
+ unsigned i = 0;
+
+ do {
+ /* Skip empty softirq groups. */
+ if (parse_softirq_group(s, next_group, i))
+ i++;
+
+ if (*next_group == '\0')
+ break;
+
+ s = next_group + 1;
+ next_group = strchrnul(s, ',');
+ } while(i < 31); /* if there is default softirq group it's nr 31 */
+
+ nr_softirq_groups = i;
+
+ return 0;
+}
+early_param("softirq_groups", setup_softirq_groups);
+
/*
* [ These __weak aliases are kept in a separate compilation unit, so that
* GCC does not inline them incorrectly. ]
--
2.13.6