rcu: Restrict access to RCU CPU stall notifiers
authorPaul E. McKenney <paulmck@kernel.org>
Thu, 2 Nov 2023 01:28:38 +0000 (18:28 -0700)
committerNeeraj Upadhyay (AMD) <neeraj.iitr10@gmail.com>
Mon, 11 Dec 2023 21:01:22 +0000 (02:31 +0530)
Although the RCU CPU stall notifiers can be useful for dumping state when
tracking down delicate forward-progress bugs where NUMA effects cause
cache lines to be delivered to a given CPU regularly, but always in a
state that prevents that CPU from making forward progress.  These bugs can
be detected by the RCU CPU stall-warning mechanism, but in some cases,
the stall-warnings printk()s disrupt the forward-progress bug before
any useful state can be obtained.

Unfortunately, the notifier mechanism added by commit 5b404fdabacf ("rcu:
Add RCU CPU stall notifier") can make matters worse if used at all
carelessly. For example, if the stall warning was caused by a lock not
being released, then any attempt to acquire that lock in the notifier
will hang. This will prevent not only the notifier from producing any
useful output, but it will also prevent the stall-warning message from
ever appearing.

This commit therefore hides this new RCU CPU stall notifier
mechanism under a new RCU_CPU_STALL_NOTIFIER Kconfig option that
depends on both DEBUG_KERNEL and RCU_EXPERT.  In addition, the
rcupdate.rcu_cpu_stall_notifiers=1 kernel boot parameter must also
be specified.  The RCU_CPU_STALL_NOTIFIER Kconfig option's help text
contains a warning and explains the dangers of careless use, recommending
lockless notifier code.  In addition, a WARN() is triggered each time
that an attempt is made to register a stall-warning notifier in kernels
built with CONFIG_RCU_CPU_STALL_NOTIFIER=y.

This combination of measures will keep use of this mechanism confined to
debug kernels and away from routine deployments.

[ paulmck: Apply Dan Carpenter feedback. ]

Fixes: 5b404fdabacf ("rcu: Add RCU CPU stall notifier")
Reported-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Reviewed-by: Joel Fernandes (Google) <joel@joelfernandes.org>
Signed-off-by: Neeraj Upadhyay (AMD) <neeraj.iitr10@gmail.com>
Documentation/admin-guide/kernel-parameters.txt
include/linux/rcu_notifier.h
kernel/rcu/Kconfig.debug
kernel/rcu/rcu.h
kernel/rcu/rcutorture.c
kernel/rcu/tree_stall.h
kernel/rcu/update.c

index 65731b060e3fef98cb97f03695ca8757ee197700..b72e2049c4876021d868035ec4fbc88e49d43429 100644 (file)
                        Dump ftrace buffer after reporting RCU CPU
                        stall warning.
 
+       rcupdate.rcu_cpu_stall_notifiers= [KNL]
+                       Provide RCU CPU stall notifiers, but see the
+                       warnings in the RCU_CPU_STALL_NOTIFIER Kconfig
+                       option's help text.  TL;DR:  You almost certainly
+                       do not want rcupdate.rcu_cpu_stall_notifiers.
+
        rcupdate.rcu_cpu_stall_suppress= [KNL]
                        Suppress RCU CPU stall warning messages.
 
index ebf371364581d540639f268191be2316c66d23bb..5640f024773b3762d75a72e03b87b1624e0e5d84 100644 (file)
@@ -13,7 +13,7 @@
 #define RCU_STALL_NOTIFY_NORM  1
 #define RCU_STALL_NOTIFY_EXP   2
 
-#ifdef CONFIG_RCU_STALL_COMMON
+#if defined(CONFIG_RCU_STALL_COMMON) && defined(CONFIG_RCU_CPU_STALL_NOTIFIER)
 
 #include <linux/notifier.h>
 #include <linux/types.h>
 int rcu_stall_chain_notifier_register(struct notifier_block *n);
 int rcu_stall_chain_notifier_unregister(struct notifier_block *n);
 
-#else // #ifdef CONFIG_RCU_STALL_COMMON
+#else // #if defined(CONFIG_RCU_STALL_COMMON) && defined(CONFIG_RCU_CPU_STALL_NOTIFIER)
 
 // No RCU CPU stall warnings in Tiny RCU.
 static inline int rcu_stall_chain_notifier_register(struct notifier_block *n) { return -EEXIST; }
 static inline int rcu_stall_chain_notifier_unregister(struct notifier_block *n) { return -ENOENT; }
 
-#endif // #else // #ifdef CONFIG_RCU_STALL_COMMON
+#endif // #else // #if defined(CONFIG_RCU_STALL_COMMON) && defined(CONFIG_RCU_CPU_STALL_NOTIFIER)
 
 #endif /* __LINUX_RCU_NOTIFIER_H */
index 2984de629f7494d6dde1e936197eca987520c69f..9b0b52e1836fa42b60539edcd33f0c70358b74f2 100644 (file)
@@ -105,6 +105,31 @@ config RCU_CPU_STALL_CPUTIME
          The boot option rcupdate.rcu_cpu_stall_cputime has the same function
          as this one, but will override this if it exists.
 
+config RCU_CPU_STALL_NOTIFIER
+       bool "Provide RCU CPU-stall notifiers"
+       depends on RCU_STALL_COMMON
+       depends on DEBUG_KERNEL
+       depends on RCU_EXPERT
+       default n
+       help
+         WARNING:  You almost certainly do not want this!!!
+
+         Enable RCU CPU-stall notifiers, which are invoked just before
+         printing the RCU CPU stall warning.  As such, bugs in notifier
+         callbacks can prevent stall warnings from being printed.
+         And the whole reason that a stall warning is being printed is
+         that something is hung up somewhere.  Therefore, the notifier
+         callbacks must be written extremely carefully, preferably
+         containing only lockless code.  After all, it is quite possible
+         that the whole reason that the RCU CPU stall is happening in
+         the first place is that someone forgot to release whatever lock
+         that you are thinking of acquiring.  In which case, having your
+         notifier callback acquire that lock will hang, preventing the
+         RCU CPU stall warning from appearing.
+
+         Say Y here if you want RCU CPU stall notifiers (you don't want them)
+         Say N if you are unsure.
+
 config RCU_TRACE
        bool "Enable tracing for RCU"
        depends on DEBUG_KERNEL
index b531c33e9545b7957599fc67a7d9af3193f1279f..f94f65877f2b68055b4e5c7a057c22bf4fb96a18 100644 (file)
@@ -262,6 +262,8 @@ static inline bool rcu_stall_is_suppressed_at_boot(void)
        return rcu_cpu_stall_suppress_at_boot && !rcu_inkernel_boot_has_ended();
 }
 
+extern int rcu_cpu_stall_notifiers;
+
 #ifdef CONFIG_RCU_STALL_COMMON
 
 extern int rcu_cpu_stall_ftrace_dump;
@@ -659,10 +661,10 @@ static inline bool rcu_cpu_beenfullyonline(int cpu) { return true; }
 bool rcu_cpu_beenfullyonline(int cpu);
 #endif
 
-#ifdef CONFIG_RCU_STALL_COMMON
+#if defined(CONFIG_RCU_STALL_COMMON) && defined(CONFIG_RCU_CPU_STALL_NOTIFIER)
 int rcu_stall_notifier_call_chain(unsigned long val, void *v);
-#else // #ifdef CONFIG_RCU_STALL_COMMON
+#else // #if defined(CONFIG_RCU_STALL_COMMON) && defined(CONFIG_RCU_CPU_STALL_NOTIFIER)
 static inline int rcu_stall_notifier_call_chain(unsigned long val, void *v) { return NOTIFY_DONE; }
-#endif // #else // #ifdef CONFIG_RCU_STALL_COMMON
+#endif // #else // #if defined(CONFIG_RCU_STALL_COMMON) && defined(CONFIG_RCU_CPU_STALL_NOTIFIER)
 
 #endif /* __LINUX_RCU_H */
index 30fc9d34e3297f86c822b1db5face539af2ee83d..07a6a183c5556672f2bde7be51daebb9cb483bf6 100644 (file)
@@ -2450,10 +2450,12 @@ static int rcu_torture_stall(void *args)
        unsigned long stop_at;
 
        VERBOSE_TOROUT_STRING("rcu_torture_stall task started");
-       ret = rcu_stall_chain_notifier_register(&rcu_torture_stall_block);
-       if (ret)
-               pr_info("%s: rcu_stall_chain_notifier_register() returned %d, %sexpected.\n",
-                       __func__, ret, !IS_ENABLED(CONFIG_RCU_STALL_COMMON) ? "un" : "");
+       if (rcu_cpu_stall_notifiers) {
+               ret = rcu_stall_chain_notifier_register(&rcu_torture_stall_block);
+               if (ret)
+                       pr_info("%s: rcu_stall_chain_notifier_register() returned %d, %sexpected.\n",
+                               __func__, ret, !IS_ENABLED(CONFIG_RCU_STALL_COMMON) ? "un" : "");
+       }
        if (stall_cpu_holdoff > 0) {
                VERBOSE_TOROUT_STRING("rcu_torture_stall begin holdoff");
                schedule_timeout_interruptible(stall_cpu_holdoff * HZ);
@@ -2497,7 +2499,7 @@ static int rcu_torture_stall(void *args)
                cur_ops->readunlock(idx);
        }
        pr_alert("%s end.\n", __func__);
-       if (!ret) {
+       if (rcu_cpu_stall_notifiers && !ret) {
                ret = rcu_stall_chain_notifier_unregister(&rcu_torture_stall_block);
                if (ret)
                        pr_info("%s: rcu_stall_chain_notifier_unregister() returned %d.\n", __func__, ret);
index ac8e86babe449a20b1e0bd03a7d87bfb5b7ff579..5d666428546b03ac9e865fbc6e1ae9b1e057100f 100644 (file)
@@ -1061,6 +1061,7 @@ static int __init rcu_sysrq_init(void)
 }
 early_initcall(rcu_sysrq_init);
 
+#ifdef CONFIG_RCU_CPU_STALL_NOTIFIER
 
 //////////////////////////////////////////////////////////////////////////////
 //
@@ -1081,7 +1082,13 @@ static ATOMIC_NOTIFIER_HEAD(rcu_cpu_stall_notifier_list);
  */
 int rcu_stall_chain_notifier_register(struct notifier_block *n)
 {
-       return atomic_notifier_chain_register(&rcu_cpu_stall_notifier_list, n);
+       int rcsn = rcu_cpu_stall_notifiers;
+
+       WARN(1, "Adding %pS() to RCU stall notifier list (%s).\n", n->notifier_call,
+            rcsn ? "possibly suppressing RCU CPU stall warnings" : "failed, so all is well");
+       if (rcsn)
+               return atomic_notifier_chain_register(&rcu_cpu_stall_notifier_list, n);
+       return -EEXIST;
 }
 EXPORT_SYMBOL_GPL(rcu_stall_chain_notifier_register);
 
@@ -1115,3 +1122,5 @@ int rcu_stall_notifier_call_chain(unsigned long val, void *v)
 {
        return atomic_notifier_call_chain(&rcu_cpu_stall_notifier_list, val, v);
 }
+
+#endif // #ifdef CONFIG_RCU_CPU_STALL_NOTIFIER
index c534d6806d3d5726322d5da634224cd6897293cc..46aaaa9fe33907b0eddb70e2b1c5d6a2419c13c8 100644 (file)
@@ -538,9 +538,15 @@ long torture_sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
 EXPORT_SYMBOL_GPL(torture_sched_setaffinity);
 #endif
 
+int rcu_cpu_stall_notifiers __read_mostly; // !0 = provide stall notifiers (rarely useful)
+EXPORT_SYMBOL_GPL(rcu_cpu_stall_notifiers);
+
 #ifdef CONFIG_RCU_STALL_COMMON
 int rcu_cpu_stall_ftrace_dump __read_mostly;
 module_param(rcu_cpu_stall_ftrace_dump, int, 0644);
+#ifdef CONFIG_RCU_CPU_STALL_NOTIFIER
+module_param(rcu_cpu_stall_notifiers, int, 0444);
+#endif // #ifdef CONFIG_RCU_CPU_STALL_NOTIFIER
 int rcu_cpu_stall_suppress __read_mostly; // !0 = suppress stall warnings.
 EXPORT_SYMBOL_GPL(rcu_cpu_stall_suppress);
 module_param(rcu_cpu_stall_suppress, int, 0644);