Blob Blame History Raw
From b0db76baf6ff2a71585d708d09b5ca27d36922a8 Mon Sep 17 00:00:00 2001
From: Michal Hocko <mhocko@suse.com>
Date: Wed, 7 Oct 2020 10:35:12 +0200
Subject: [PATCH] kernel: allow to configure PREEMPT_NONE, PREEMPT_VOLUNTARY on
 kernel command line
Patch-mainline: not yet, needs more work for upstream inclusion
References: jsc#SLE-16775

mhocko@suse.com:
this has been posted upstream http://lkml.kernel.org/r/20201007120401.11200-1-mhocko@kernel.org
and upstream maintainers would like to see the full preemption mode
in the mix as well. This is desirable for us long term as well but this
is more work and likely not in time for 15sp3. There is a general
agreement on the command line interface though. The final implementation
might differ but we won't be able to change anything after kABI freeze
as a lot of work is in header files. I do assume that this patch will
be replaced by an upstream variant in the next service pack.

Many people are still relying on pre built distribution kernels and so
distributions have to provide mutliple kernel flavors to offer different
preemption models. Most of them are providing PREEMPT_NONE for typical
server deployments and PREEMPT_VOLUNTARY for desktop users.

Having two different kernel binaries differing only by the preemption
mode seems rather wasteful and inflexible. Especially when the difference
between PREEMPT_NONE and PREEMPT_VOLUNTARY is really minimal. Both only
allow explicit scheduling points while running in the kernel and it is
only might_sleep which adds additional preemption points for
PREEMPT_VOLUNTARY.

Add a kernel command line parameter preempt=[none, voluntary] which
allows to override the default compile time preemption mode
(CONFIG_PREEMPT_NONE resp. CONFIG_PREEMPT_VOLUTARY). Voluntary preempt
mode is guarded by a jump label to prevent any potential runtime overhead.

TODO move jump label changes into their own patch. They are needed
mostly to get rid of header files dependencies (seen for
CONFIG_JUMP_LABEL=n via atomic.h -> bug.h).

Signed-off-by: Michal Hocko <mhocko@suse.com>

---
 Documentation/admin-guide/kernel-parameters.txt |    5 ++
 include/linux/gpio/consumer.h                   |    1 
 include/linux/jump_label.h                      |   44 ---------------------
 include/linux/jump_label_type.h                 |   49 ++++++++++++++++++++++++
 include/linux/kernel.h                          |   10 ++++
 include/linux/list.h                            |    1 
 kernel/sched/core.c                             |   30 ++++++++++++++
 7 files changed, 96 insertions(+), 44 deletions(-)
 create mode 100644 include/linux/jump_label_type.h

--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -3692,6 +3692,11 @@
 			Format: {"on"}
 			Enable Hardware Transactional Memory
 
+	preempt={none,voluntary}
+			Set the preemption mode.
+			none - equivalent to CONFIG_PREEMPT_NONE
+			voluntary - equivalent to CONFIG_PREEMPT_VOLUNTARY
+
 	print-fatal-signals=
 			[KNL] debug: print fatal signals
 
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -2,6 +2,7 @@
 #ifndef __LINUX_GPIO_CONSUMER_H
 #define __LINUX_GPIO_CONSUMER_H
 
+#include <linux/jump_label.h>
 #include <linux/bug.h>
 #include <linux/err.h>
 #include <linux/kernel.h>
--- a/include/linux/jump_label.h
+++ b/include/linux/jump_label.h
@@ -75,6 +75,7 @@
 
 #include <linux/types.h>
 #include <linux/compiler.h>
+#include <linux/jump_label_type.h>
 
 extern bool static_key_initialized;
 
@@ -82,35 +83,6 @@ extern bool static_key_initialized;
 				    "%s(): static key '%pS' used before call to jump_label_init()", \
 				    __func__, (key))
 
-#ifdef CONFIG_JUMP_LABEL
-
-struct static_key {
-	atomic_t enabled;
-/*
- * Note:
- *   To make anonymous unions work with old compilers, the static
- *   initialization of them requires brackets. This creates a dependency
- *   on the order of the struct with the initializers. If any fields
- *   are added, STATIC_KEY_INIT_TRUE and STATIC_KEY_INIT_FALSE may need
- *   to be modified.
- *
- * bit 0 => 1 if key is initially true
- *	    0 if initially false
- * bit 1 => 1 if points to struct static_key_mod
- *	    0 if points to struct jump_entry
- */
-	union {
-		unsigned long type;
-		struct jump_entry *entries;
-		struct static_key_mod *next;
-	};
-};
-
-#else
-struct static_key {
-	atomic_t enabled;
-};
-#endif	/* CONFIG_JUMP_LABEL */
 #endif /* __ASSEMBLY__ */
 
 #ifdef CONFIG_JUMP_LABEL
@@ -343,14 +315,6 @@ static inline void static_key_disable(st
  * All the below code is macros in order to play type games.
  */
 
-struct static_key_true {
-	struct static_key key;
-};
-
-struct static_key_false {
-	struct static_key key;
-};
-
 #define STATIC_KEY_TRUE_INIT  (struct static_key_true) { .key = STATIC_KEY_INIT_TRUE,  }
 #define STATIC_KEY_FALSE_INIT (struct static_key_false){ .key = STATIC_KEY_INIT_FALSE, }
 
@@ -360,18 +324,12 @@ struct static_key_false {
 #define DEFINE_STATIC_KEY_TRUE_RO(name)	\
 	struct static_key_true name __ro_after_init = STATIC_KEY_TRUE_INIT
 
-#define DECLARE_STATIC_KEY_TRUE(name)	\
-	extern struct static_key_true name
-
 #define DEFINE_STATIC_KEY_FALSE(name)	\
 	struct static_key_false name = STATIC_KEY_FALSE_INIT
 
 #define DEFINE_STATIC_KEY_FALSE_RO(name)	\
 	struct static_key_false name __ro_after_init = STATIC_KEY_FALSE_INIT
 
-#define DECLARE_STATIC_KEY_FALSE(name)	\
-	extern struct static_key_false name
-
 #define DEFINE_STATIC_KEY_ARRAY_TRUE(name, count)		\
 	struct static_key_true name[count] = {			\
 		[0 ... (count) - 1] = STATIC_KEY_TRUE_INIT,	\
--- /dev/null
+++ b/include/linux/jump_label_type.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_JUMP_LABEL_TYPE_H
+#define _LINUX_JUMP_LABEL_TYPE_H
+
+#ifdef CONFIG_JUMP_LABEL
+
+struct static_key {
+	atomic_t enabled;
+/*
+ * Note:
+ *   To make anonymous unions work with old compilers, the static
+ *   initialization of them requires brackets. This creates a dependency
+ *   on the order of the struct with the initializers. If any fields
+ *   are added, STATIC_KEY_INIT_TRUE and STATIC_KEY_INIT_FALSE may need
+ *   to be modified.
+ *
+ * bit 0 => 1 if key is initially true
+ *	    0 if initially false
+ * bit 1 => 1 if points to struct static_key_mod
+ *	    0 if points to struct jump_entry
+ */
+	union {
+		unsigned long type;
+		struct jump_entry *entries;
+		struct static_key_mod *next;
+	};
+};
+
+#else
+struct static_key {
+	atomic_t enabled;
+};
+#endif	/* CONFIG_JUMP_LABEL */
+
+struct static_key_true {
+	struct static_key key;
+};
+
+struct static_key_false {
+	struct static_key key;
+};
+
+#define DECLARE_STATIC_KEY_TRUE(name)	\
+	extern struct static_key_true name
+
+#define DECLARE_STATIC_KEY_FALSE(name)	\
+	extern struct static_key_false name
+
+#endif /* _LINUX_JUMP_LABEL_TYPE_H */
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -14,6 +14,7 @@
 #include <linux/typecheck.h>
 #include <linux/printk.h>
 #include <linux/build_bug.h>
+#include <linux/jump_label_type.h>
 #include <asm/byteorder.h>
 #include <asm/div64.h>
 #include <uapi/linux/kernel.h>
@@ -200,9 +201,16 @@ struct completion;
 struct pt_regs;
 struct user;
 
+#ifndef CONFIG_PREEMPTION
 #ifdef CONFIG_PREEMPT_VOLUNTARY
+DECLARE_STATIC_KEY_TRUE(preempt_voluntary_key);
+#else
+DECLARE_STATIC_KEY_FALSE(preempt_voluntary_key);
+#endif
+
 extern int _cond_resched(void);
-# define might_resched() _cond_resched()
+# define might_resched() \
+	do { if (static_branch_likely(&preempt_voluntary_key)) _cond_resched(); } while (0)
 #else
 # define might_resched() do { } while (0)
 #endif
--- a/include/linux/list.h
+++ b/include/linux/list.h
@@ -6,6 +6,7 @@
 #include <linux/stddef.h>
 #include <linux/poison.h>
 #include <linux/const.h>
+#include <linux/jump_label.h>
 #include <linux/kernel.h>
 
 /*
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -40,6 +40,14 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_updat
 
 DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
 
+#ifdef CONFIG_PREEMPT_VOLUNTARY
+DEFINE_STATIC_KEY_TRUE(preempt_voluntary_key);
+#else
+/* PREEMPT_NONE vs PREEMPT_VOLUNTARY */
+DEFINE_STATIC_KEY_FALSE(preempt_voluntary_key);
+#endif
+EXPORT_SYMBOL(preempt_voluntary_key);
+
 #if defined(CONFIG_SCHED_DEBUG) && defined(CONFIG_JUMP_LABEL)
 /*
  * Debugging: various feature bits
@@ -7870,3 +7878,25 @@ void call_trace_sched_update_nr_running(
 {
         trace_sched_update_nr_running_tp(rq, count);
 }
+
+#ifndef CONFIG_PREEMPTION
+static int __init setup_non_preempt(char *str)
+{
+	if (!strcmp(str, "none")) {
+		if (IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY)) {
+			static_branch_disable(&preempt_voluntary_key);
+			pr_info("Switching to PREEMPT_NONE mode.");
+		}
+	} else if (!strcmp(str, "voluntary")) {
+		if (!IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY)) {
+			static_branch_enable(&preempt_voluntary_key);
+			pr_info("Switching to PREEMPT_VOLUNTARY mode.");
+		}
+	} else {
+		pr_warn("Unsupported preempt mode %s\n", str);
+		return 1;
+	}
+	return 0;
+}
+__setup("preempt=", setup_non_preempt);
+#endif