RCU fixes for v6.7
Subsystems (13)
Frederic Weisbecker
maintainercover RCU fixes for v6.7
Hello,
Please find below the general (S)RCU fixes:
Catalin Marinas (1):
rcu: kmemleak: Ignore kmemleak false positives when RCU-freeing
objects
Denis Arefev (1):
srcu: Fix srcu_struct node grpmask overflow on 64-bit systems
Frederic Weisbecker (8):
rcu: Use rcu_segcblist_segempty() instead of open coding it
rcu: Assume IRQS disabled from rcu_report_dead()
rcu: Assume rcu_report_dead() is always called locally
rcu: Conditionally build CPU-hotplug teardown callbacks
rcu: Standardize explicit CPU-hotplug calls
rcu: Comment why callbacks migration can't wait for CPUHP_RCUTREE_PREP
srcu: Fix callbacks acceleration mishandling
srcu: Only accelerate on enqueue time
Joel Fernandes (Google) (3):
Revert "checkpatch: Error out if deprecated RCU API used"
srcu: Fix error handling in init_srcu_struct_fields() rcu/tree: Remove superfluous return from void call_rcu* functions
Paul E. McKenney (2):
rcu: Add sysfs to provide throttled access to rcu_barrier()
rcu: Eliminate rcu_gp_slow_unregister() false positive
Yue Haibing (1):
rcu: Remove unused function declaration rcu_eqs_special_set()
Zhen Lei (2):
mm: Remove kmem_valid_obj()
rcu: Dump memory object info if callback function is invalid
.../Expedited-Grace-Periods.rst | 2 +- .../Design/Memory-Ordering/TreeRCU-gp-fqs.svg | 4 +- .../RCU/Design/Memory-Ordering/TreeRCU-gp.svg | 4 +- .../Memory-Ordering/TreeRCU-hotplug.svg | 4 +- .../RCU/Design/Requirements/Requirements.rst | 4 +- .../admin-guide/kernel-parameters.txt | 7 + arch/arm64/kernel/smp.c | 4 +- arch/powerpc/kernel/smp.c | 2 +- arch/s390/kernel/smp.c | 2 +- arch/x86/kernel/smpboot.c | 2 +- include/linux/interrupt.h | 2 +- include/linux/rcupdate.h | 2 - include/linux/rcutiny.h | 2 +- include/linux/rcutree.h | 17 +- include/linux/slab.h | 5 +- kernel/cpu.c | 13 +- kernel/rcu/rcu.h | 7 + kernel/rcu/rcu_segcblist.c | 4 +- kernel/rcu/srcutiny.c | 1 + kernel/rcu/srcutree.c | 76 ++++-- kernel/rcu/tasks.h | 1 + kernel/rcu/tiny.c | 1 + kernel/rcu/tree.c | 230 ++++++++++++------ mm/slab_common.c | 41 +--- mm/util.c | 4 +- scripts/checkpatch.pl | 9 - 26 files changed, 284 insertions(+), 166 deletions(-)
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 1/18 Revert "checkpatch: Error out if deprecated RCU API used"
From: "Joel Fernandes (Google)" <[email protected]>
The definition for single-argument kfree_rcu() has been removed, so that any further attempt to use it will result in a build error. Because of this build error, there is no longer any need for a special check in checkpatch.pl.
Therefore, revert commit 1eacac3255495be7502d406e2ba5444fb5c3607c.
--- scripts/checkpatch.pl | 9 --------- 1 file changed, 9 deletions(-)
scripts/checkpatch.pl +0 -9
@@ -6427,15 +6427,6 @@ sub process { } } -# check for soon-to-be-deprecated single-argument k[v]free_rcu() API - if ($line =~ /\bk[v]?free_rcu\s*\([^(]+\)/) { - if ($line =~ /\bk[v]?free_rcu\s*\([^,]+\)/) { - ERROR("DEPRECATED_API", - "Single-argument k[v]free_rcu() API is deprecated, please pass rcu_head object or call k[v]free_rcu_mightsleep()." . $herecurr); - } - } - - # check for unnecessary "Out of Memory" messages if ($line =~ /^\+.*\b$logFunctions\s*\(/ && $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ &&
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 2/18 srcu: Fix error handling in init_srcu_struct_fields()
In reply to Frederic WeisbeckerFrom: "Joel Fernandes (Google)" <[email protected]>
The current error handling in init_srcu_struct_fields() is a bit inconsistent. If init_srcu_struct_nodes() fails, the function either returns -ENOMEM or 0 depending on whether ssp->sda_is_static is true or false. This can make init_srcu_struct_fields() return 0 even if memory allocation failed!
Simplify the error handling by always returning -ENOMEM if either init_srcu_struct_nodes() or the per-CPU allocation fails. This makes the control flow easier to follow and avoids the inconsistent return values.
Add goto labels to avoid duplicating the error cleanup code.
--- kernel/rcu/srcutree.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-)
kernel/rcu/srcutree.c +17 -15
@@ -255,29 +255,31 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp, bool is_static) ssp->srcu_sup->sda_is_static = is_static;if (!is_static) ssp->sda = alloc_percpu(struct srcu_data); -if (!ssp->sda) { -if (!is_static) -kfree (ssp->srcu_sup); -return -ENOMEM ; - } +if (!ssp->sda) + goto err_free_sup;init_srcu_struct_data (ssp); ssp->srcu_sup->srcu_gp_seq_needed_exp =0 ; ssp->srcu_sup->srcu_last_gp_end =ktime_get_mono_fast_ns ();if (READ_ONCE (ssp->srcu_sup->srcu_size_state) ==SRCU_SIZE_SMALL &&SRCU_SIZING_IS_INIT ()) { -if (!init_srcu_struct_nodes (ssp,GFP_ATOMIC )) { -if (!ssp->srcu_sup->sda_is_static) { -free_percpu (ssp->sda); - ssp->sda =NULL ; -kfree (ssp->srcu_sup); -return -ENOMEM ; - } - }else { -WRITE_ONCE (ssp->srcu_sup->srcu_size_state,SRCU_SIZE_BIG ); - } +if (!init_srcu_struct_nodes (ssp,GFP_ATOMIC )) + goto err_free_sda; +WRITE_ONCE (ssp->srcu_sup->srcu_size_state,SRCU_SIZE_BIG ); } ssp->srcu_sup->srcu_ssp = ssp;smp_store_release (&ssp->srcu_sup->srcu_gp_seq_needed,0 );/* Init done. */ return 0 ; + +err_free_sda: +if (!is_static) { +free_percpu (ssp->sda); + ssp->sda =NULL ; + } +err_free_sup: +if (!is_static) { +kfree (ssp->srcu_sup); + ssp->srcu_sup =NULL ; + } +return -ENOMEM ; }#ifdef CONFIG_DEBUG_LOCK_ALLOC
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 3/18 rcu/tree: Remove superfluous return from void call_rcu* functions
In reply to Frederic WeisbeckerFrom: "Joel Fernandes (Google)" <[email protected]>
The return keyword is not needed here.
--- kernel/rcu/tree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
kernel/rcu/tree.c +2 -2
@@ -2713,7 +2713,7 @@ __call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)*/ void call_rcu_hurry (struct rcu_head *head,rcu_callback_t func) { -return __call_rcu_common (head, func, false); +__call_rcu_common (head, func, false); }EXPORT_SYMBOL_GPL (call_rcu_hurry);#endif
@@ -2764,7 +2764,7 @@ EXPORT_SYMBOL_GPL(call_rcu_hurry);*/ void call_rcu (struct rcu_head *head,rcu_callback_t func) { -return __call_rcu_common (head, func,IS_ENABLED (CONFIG_RCU_LAZY )); +__call_rcu_common (head, func,IS_ENABLED (CONFIG_RCU_LAZY )); }EXPORT_SYMBOL_GPL (call_rcu);
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 4/18 rcu: Add sysfs to provide throttled access to rcu_barrier()
In reply to Frederic WeisbeckerFrom: "Paul E. McKenney" <[email protected]>
When running a series of stress tests all making heavy use of RCU, it is all too possible to OOM the system when the prior test's RCU callbacks don't get invoked until after the subsequent test starts. One way of handling this is just a timed wait, but this fails when a given CPU has so many callbacks queued that they take longer to invoke than allowed for by that timed wait.
This commit therefore adds an rcutree.do_rcu_barrier module parameter that is accessible from sysfs. Writing one of the many synonyms for boolean "true" will cause an rcu_barrier() to be invoked, but will guarantee that no more than one rcu_barrier() will be invoked per sixteenth of a second via this mechanism. The flip side is that a given request might wait a second or three longer than absolutely necessary, but only when there are multiple uses of rcutree.do_rcu_barrier within a one-second time interval.
This commit unnecessarily serializes the rcu_barrier() machinery, given that serialization is already provided by procfs. This has the advantage of allowing throttled rcu_barrier() from other sources within the kernel.
--- .../admin-guide/kernel-parameters.txt | 7 ++ kernel/rcu/tree.c | 76 +++++++++++++++++++ 2 files changed, 83 insertions(+)
Documentation/admin-guide/kernel-parameters.txt +7 -0
@@ -4769,6 +4769,13 @@ Set maximum number of finished RCU callbacks to process in one batch. + rcutree.do_rcu_barrier= [KNL] + Request a call to rcu_barrier(). This is + throttled so that userspace tests can safely + hammer on the sysfs variable if they so choose. + If triggered before the RCU grace-period machinery + is fully active, this will error out with EAGAIN. + rcutree.dump_tree= [KNL] Dump the structure of the rcu_node combining tree out at early boot. This is used for diagnostic
kernel/rcu/tree.c +76 -0
82 lines not on this page · raw message
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 5/18 rcu: Remove unused function declaration rcu_eqs_special_set()
In reply to Frederic WeisbeckerFrom: Yue Haibing <[email protected]>
Commit a86baa69c2b7 ("rcu: Remove special bit at the bottom of the ->dynticks counter") left behind this, remove it.
--- include/linux/rcutree.h | 1 - 1 file changed, 1 deletion(-)
include/linux/rcutree.h +0 -1
@@ -37,7 +37,6 @@ void synchronize_rcu_expedited(void);void kvfree_call_rcu (struct rcu_head *head,void *ptr);void rcu_barrier (void ); -bool rcu_eqs_special_set (int cpu);void rcu_momentary_dyntick_idle (void );void kfree_rcu_scheduler_running (void );bool rcu_gp_might_be_stalled (void );
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 6/18 mm: Remove kmem_valid_obj()
In reply to Frederic WeisbeckerFrom: Zhen Lei <[email protected]>
Function kmem_dump_obj() will splat if passed a pointer to a non-slab object. So nothing calls it directly, instead calling kmem_valid_obj() first to determine whether the passed pointer to a valid slab object. This means that merging kmem_valid_obj() into kmem_dump_obj() will make the code more concise. Therefore, convert kmem_dump_obj() to work the same way as vmalloc_dump_obj(), removing the need for the kmem_dump_obj() caller to check kmem_valid_obj(). After this, there are no remaining calls to kmem_valid_obj() anymore, and it can be safely removed.
--- include/linux/slab.h | 5 +++-- mm/slab_common.c | 41 +++++++++++------------------------------ mm/util.c | 4 +--- 3 files changed, 15 insertions(+), 35 deletions(-)
include/linux/slab.h +3 -2
@@ -245,8 +245,9 @@ DEFINE_FREE(kfree, void *, if (_T) kfree(_T))size_t ksize (const void *objp);#ifdef CONFIG_PRINTK -bool kmem_valid_obj (void *object); -void kmem_dump_obj (void *object); +bool kmem_dump_obj (void *object); +#else +static inline bool kmem_dump_obj (void *object) {return false; }#endif /*
mm/slab_common.c +11 -30
67 lines not on this page · raw message
mm/util.c +1 -3
@@ -1060,10 +1060,8 @@ void mem_dump_obj(void *object) {const char *type; -if (kmem_valid_obj (object)) { -kmem_dump_obj (object); +if (kmem_dump_obj (object))return ; - }if (vmalloc_dump_obj (object))return ;
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 7/18 rcu: Dump memory object info if callback function is invalid
In reply to Frederic WeisbeckerFrom: Zhen Lei <[email protected]>
When a structure containing an RCU callback rhp is (incorrectly) freed and reallocated after rhp is passed to call_rcu(), it is not unusual for rhp->func to be set to NULL. This defeats the debugging prints used by __call_rcu_common() in kernels built with CONFIG_DEBUG_OBJECTS_RCU_HEAD=y, which expect to identify the offending code using the identity of this function.
And in kernels build without CONFIG_DEBUG_OBJECTS_RCU_HEAD=y, things are even worse, as can be seen from this splat:
Unable to handle kernel NULL pointer dereference at virtual address 0
... ...
PC is at 0x0
LR is at rcu_do_batch+0x1c0/0x3b8
... ...
(rcu_do_batch) from (rcu_core+0x1d4/0x284) (rcu_core) from (__do_softirq+0x24c/0x344) (__do_softirq) from (__irq_exit_rcu+0x64/0x108) (__irq_exit_rcu) from (irq_exit+0x8/0x10) (irq_exit) from (__handle_domain_irq+0x74/0x9c) (__handle_domain_irq) from (gic_handle_irq+0x8c/0x98) (gic_handle_irq) from (__irq_svc+0x5c/0x94) (__irq_svc) from (arch_cpu_idle+0x20/0x3c) (arch_cpu_idle) from (default_idle_call+0x4c/0x78) (default_idle_call) from (do_idle+0xf8/0x150) (do_idle) from (cpu_startup_entry+0x18/0x20) (cpu_startup_entry) from (0xc01530)
This commit therefore adds calls to mem_dump_obj(rhp) to output some information, for example:
slab kmalloc-256 start ffff410c45019900 pointer offset 0 size 256
This provides the rough size of the memory block and the offset of the rcu_head structure, which as least provides at least a few clues to help locate the problem. If the problem is reproducible, additional slab debugging can be enabled, for example, CONFIG_DEBUG_SLAB=y, which can provide significantly more information.
--- kernel/rcu/rcu.h | 7 +++++++ kernel/rcu/srcutiny.c | 1 + kernel/rcu/srcutree.c | 1 + kernel/rcu/tasks.h | 1 + kernel/rcu/tiny.c | 1 + kernel/rcu/tree.c | 1 + 6 files changed, 12 insertions(+)
kernel/rcu/rcu.h +7 -0
@@ -10,6 +10,7 @@#ifndef __LINUX_RCU_H#define __LINUX_RCU_H +#include <linux/slab.h> #include <trace/events/rcu.h> /*
@@ -248,6 +249,12 @@ static inline void debug_rcu_head_unqueue(struct rcu_head *head) }#endif /* #else !CONFIG_DEBUG_OBJECTS_RCU_HEAD */ +static inline void debug_rcu_head_callback (struct rcu_head *rhp) +{ +if (unlikely (!rhp->func)) +kmem_dump_obj (rhp); +} +extern int rcu_cpu_stall_suppress_at_boot;static inline bool rcu_stall_is_suppressed_at_boot (void )
kernel/rcu/srcutiny.c +1 -0
@@ -138,6 +138,7 @@ void srcu_drive_gp(struct work_struct *wp)while (lh) { rhp = lh; lh = lh->next; +debug_rcu_head_callback (rhp);local_bh_disable (); rhp->func (rhp);local_bh_enable ();
kernel/rcu/srcutree.c +1 -0
@@ -1710,6 +1710,7 @@ static void srcu_invoke_callbacks(struct work_struct *work) rhp =rcu_cblist_dequeue (&ready_cbs);for (; rhp !=NULL ; rhp =rcu_cblist_dequeue (&ready_cbs)) {debug_rcu_head_unqueue (rhp); +debug_rcu_head_callback (rhp);local_bh_disable (); rhp->func (rhp);local_bh_enable ();
kernel/rcu/tasks.h +1 -0
@@ -538,6 +538,7 @@ static void rcu_tasks_invoke_cbs(struct rcu_tasks *rtp, struct rcu_tasks_percpuraw_spin_unlock_irqrestore_rcu_node (rtpcp, flags); len = rcl.len;for (rhp =rcu_cblist_dequeue (&rcl); rhp; rhp =rcu_cblist_dequeue (&rcl)) { +debug_rcu_head_callback (rhp);local_bh_disable (); rhp->func (rhp);local_bh_enable ();
kernel/rcu/tiny.c +1 -0
@@ -97,6 +97,7 @@ static inline bool rcu_reclaim_tiny(struct rcu_head *head)trace_rcu_invoke_callback ("" , head); f = head->func; +debug_rcu_head_callback (head);WRITE_ONCE (head->func, (rcu_callback_t )0L );f (head);rcu_lock_release (&rcu_callback_map);
kernel/rcu/tree.c +1 -0
@@ -2135,6 +2135,7 @@ static void rcu_do_batch(struct rcu_data *rdp)trace_rcu_invoke_callback (rcu_state.name, rhp); f = rhp->func; +debug_rcu_head_callback (rhp);WRITE_ONCE (rhp->func, (rcu_callback_t )0L );f (rhp);
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 8/18 rcu: Eliminate rcu_gp_slow_unregister() false positive
In reply to Frederic WeisbeckerFrom: "Paul E. McKenney" <[email protected]>
When using rcutorture as a module, there are a number of conditions that can abort the modprobe operation, for example, when attempting to run both RCU CPU stall warning tests and forward-progress tests. This can cause rcu_torture_cleanup() to be invoked on the unwind path out of rcu_rcu_torture_init(), which will mean that rcu_gp_slow_unregister() is invoked without a matching rcu_gp_slow_register(). This will cause a splat because rcu_gp_slow_unregister() is passed rcu_fwd_cb_nodelay, which does not match a NULL pointer.
This commit therefore forgives a mismatch involving a NULL pointer, thus avoiding this false-positive splat.
--- kernel/rcu/tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/rcu/tree.c +1 -1
@@ -1260,7 +1260,7 @@ EXPORT_SYMBOL_GPL(rcu_gp_slow_register);/* Unregister a counter, with NULL for not caring which. */ void rcu_gp_slow_unregister (atomic_t *rgssp) { -WARN_ON_ONCE (rgssp && rgssp != rcu_gp_slow_suppress); +WARN_ON_ONCE (rgssp && rgssp != rcu_gp_slow_suppress && rcu_gp_slow_suppress !=NULL );WRITE_ONCE (rcu_gp_slow_suppress,NULL ); }
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 9/18 srcu: Fix srcu_struct node grpmask overflow on 64-bit systems
In reply to Frederic WeisbeckerFrom: Denis Arefev <[email protected]>
The value of a bitwise expression 1 << (cpu - sdp->mynode->grplo) is subject to overflow due to a failure to cast operands to a larger data type before performing the bitwise operation.
The maximum result of this subtraction is defined by the RCU_FANOUT_LEAF Kconfig option, which on 64-bit systems defaults to 16 (resulting in a maximum shift of 15), but which can be set up as high as 64 (resulting in a maximum shift of 63). A value of 31 can result in sign extension, resulting in 0xffffffff80000000 instead of the desired 0x80000000. A value of 32 or greater triggers undefined behavior per the C standard.
This bug has not been known to cause issues because almost all kernels take the default CONFIG_RCU_FANOUT_LEAF=16. Furthermore, as long as a given compiler gives a deterministic non-zero result for 1<<N for N>=32, the code correctly invokes all SRCU callbacks, albeit wasting CPU time along the way.
This commit therefore substitutes the correct 1UL for the buggy 1.
Found by Linux Verification Center (linuxtesting.org) with SVACE.
--- kernel/rcu/srcutree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
kernel/rcu/srcutree.c +2 -2
@@ -223,7 +223,7 @@ static bool init_srcu_struct_nodes(struct srcu_struct *ssp, gfp_t gfp_flags) snp->grplo = cpu; snp->grphi = cpu; } - sdp->grpmask =1 << (cpu - sdp->mynode->grplo); + sdp->grpmask =1UL << (cpu - sdp->mynode->grplo); }smp_store_release (&ssp->srcu_sup->srcu_size_state,SRCU_SIZE_WAIT_BARRIER );return true;
@@ -835,7 +835,7 @@ static void srcu_schedule_cbs_snp(struct srcu_struct *ssp, struct srcu_node *snpint cpu;for (cpu = snp->grplo; cpu <= snp->grphi; cpu++) { -if (!(mask & (1 << (cpu - snp->grplo)))) +if (!(mask & (1UL << (cpu - snp->grplo))))continue ;srcu_schedule_cbs_sdp (per_cpu_ptr (ssp->sda, cpu), delay); }
Signature · 1 line
2.34.1
RE: [PATCH 09/18] srcu: Fix srcu_struct node grpmask overflow on 64-bit systems
From: Frederic Weisbecker
Quoted 47 lines
Sent: 13 October 2023 12:59
The value of a bitwise expression 1 << (cpu -
sdp->mynode->grplo) is subject to overflow due to a failure to cast operands to a larger data type before performing the bitwise operation.The maximum result of this subtraction is defined by the
RCU_FANOUT_LEAFKconfigoption, which on 64-bit systems defaults to 16 (resulting in a maximum shift of 15), but which can be set up as high as 64 (resulting in a maximum shift of 63). A value of 31 can result in sign extension, resulting in0xffffffff80000000instead of the desired0x80000000. A value of 32 or greater triggers undefined behavior per the C standard.This bug has not been known to cause issues because almost all kernels take the default
CONFIG_RCU_FANOUT_LEAF=16. Furthermore, as long as a given compiler gives a deterministic non-zero result for 1<<N for N>=32, the code correctly invokes all SRCU callbacks, albeit wasting CPU time along the way.This commit therefore substitutes the correct 1UL for the buggy 1.
Found by Linux Verification Center (linuxtesting.org) with SVACE.
Signed-off-by Denis Arefev <[email protected]>Reviewed-by Mathieu Desnoyers <[email protected]>Reviewed-by Joel Fernandes (Google) <[email protected]>Cc David Laight <[email protected]>Signed-off-by Paul E. McKenney <[email protected]>Signed-off-by Frederic Weisbecker <[email protected]>--- kernel/rcu/srcutree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)kernel/rcu/srcutree.c +1
-1@@ -223,7 +223,7 @@ static bool init_srcu_struct_nodes(struct srcu_struct *ssp, gfp_t gfp_flags) snp->grplo = cpu; snp->grphi = cpu; } - sdp->grpmask =1 << (cpu - sdp->mynode->grplo); + sdp->grpmask =1UL << (cpu - sdp->mynode->grplo); }smp_store_release (&ssp->srcu_sup->srcu_size_state,SRCU_SIZE_WAIT_BARRIER );return true;
kernel/rcu/srcutree.c static void srcu_schedule_cbs_snp(struct srcu_struct *ssp, struct srcu_node *snp · line 841
Quoted 8 lines
kernel/rcu/srcutree.c +1
-1@@ -835,7 +835,7 @@ static void srcu_schedule_cbs_snp(struct srcu_struct *ssp, struct srcu_node *snpint cpu;for (cpu = snp->grplo; cpu <= snp->grphi; cpu++) { -if (!(mask & (1 << (cpu - snp->grplo)))) +if (!(mask & (1UL << (cpu - snp->grplo))))continue ;srcu_schedule_cbs_sdp (per_cpu_ptr (ssp->sda, cpu), delay); }
That loop is entirely horrid.
The compiler almost certainly has to reload snp->grphi every iteration. Also it looks as though the bottom bit of mask is checked first. So how about:
grphi = snp->grphi;
for (cpu = snp->grplo; cpu <= grphi; cpu++, mask >>= 1) {
if (!(mask & 1))
continue;
srcu_schedule_cbs_sdp(per_cpu_ptr(ssp->sda, cpu), delay);
}
David Quoted 2 lines
--
2.34.1
-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK Registration No: 1397386 (Wales)
Frederic Weisbecker
maintainerRe: [PATCH 09/18] srcu: Fix srcu_struct node grpmask overflow on 64-bit systems
Quoted 61 lines · David Laight
From: Frederic Weisbecker
Quoted 47 lines
Sent: 13 October 2023 12:59
The value of a bitwise expression 1 << (cpu -
sdp->mynode->grplo) is subject to overflow due to a failure to cast operands to a larger data type before performing the bitwise operation.The maximum result of this subtraction is defined by the
RCU_FANOUT_LEAFKconfigoption, which on 64-bit systems defaults to 16 (resulting in a maximum shift of 15), but which can be set up as high as 64 (resulting in a maximum shift of 63). A value of 31 can result in sign extension, resulting in0xffffffff80000000instead of the desired0x80000000. A value of 32 or greater triggers undefined behavior per the C standard.This bug has not been known to cause issues because almost all kernels take the default
CONFIG_RCU_FANOUT_LEAF=16. Furthermore, as long as a given compiler gives a deterministic non-zero result for 1<<N for N>=32, the code correctly invokes all SRCU callbacks, albeit wasting CPU time along the way.This commit therefore substitutes the correct 1UL for the buggy 1.
Found by Linux Verification Center (linuxtesting.org) with SVACE.
Signed-off-by Denis Arefev <[email protected]>Reviewed-by Mathieu Desnoyers <[email protected]>Reviewed-by Joel Fernandes (Google) <[email protected]>Cc David Laight <[email protected]>Signed-off-by Paul E. McKenney <[email protected]>Signed-off-by Frederic Weisbecker <[email protected]>--- kernel/rcu/srcutree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)kernel/rcu/srcutree.c +1
-1@@ -223,7 +223,7 @@ static bool init_srcu_struct_nodes(struct srcu_struct *ssp, gfp_t gfp_flags) snp->grplo = cpu; snp->grphi = cpu; } - sdp->grpmask =1 << (cpu - sdp->mynode->grplo); + sdp->grpmask =1UL << (cpu - sdp->mynode->grplo); }smp_store_release (&ssp->srcu_sup->srcu_size_state,SRCU_SIZE_WAIT_BARRIER );return true;kernel/rcu/srcutree.c static void srcu_schedule_cbs_snp(struct srcu_struct *ssp, struct srcu_node *snp · line 841
Quoted 8 lines
kernel/rcu/srcutree.c +1
-1@@ -835,7 +835,7 @@ static void srcu_schedule_cbs_snp(struct srcu_struct *ssp, struct srcu_node *snpint cpu;for (cpu = snp->grplo; cpu <= snp->grphi; cpu++) { -if (!(mask & (1 << (cpu - snp->grplo)))) +if (!(mask & (1UL << (cpu - snp->grplo))))continue ;srcu_schedule_cbs_sdp (per_cpu_ptr (ssp->sda, cpu), delay); }That loop is entirely horrid.
The compiler almost certainly has to reloadsnp->grphievery iteration. Also it looks as though the bottom bit of mask is checked first. So how about:
Quoted 6 lines
grphi = snp->grphi;
for (cpu = snp->grplo; cpu <= grphi; cpu++, mask >>= 1) {
if (!(mask & 1))
continue;
srcu_schedule_cbs_sdp(per_cpu_ptr(ssp->sda, cpu), delay);
}Well, it's cache-hot and RCU update side is not really a fast-path. Not sure it's worth optimizing...
Thanks.
Quoted 10 lines
David
Quoted 2 lines
--
2.34.1-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK Registration No: 1397386 (Wales)
Frederic Weisbecker
maintainerPATCH 10/18 rcu: kmemleak: Ignore kmemleak false positives when RCU-freeing objects
In reply to Frederic WeisbeckerFrom: Catalin Marinas <[email protected]>
Since the actual slab freeing is deferred when calling kvfree_rcu(), so is the kmemleak_free() callback informing kmemleak of the object deletion. From the perspective of the kvfree_rcu() caller, the object is freed and it may remove any references to it. Since kmemleak does not scan RCU internal data storing the pointer, it will report such objects as leaks during the grace period.
Tell kmemleak to ignore such objects on the kvfree_call_rcu() path. Note that the tiny RCU implementation does not have such issue since the objects can be tracked from the rcu_ctrlblk structure.
--- kernel/rcu/tree.c | 9 +++++++++ 1 file changed, 9 insertions(+)
kernel/rcu/tree.c +9 -0
@@ -31,6 +31,7 @@#include <linux/bitops.h> #include <linux/export.h> #include <linux/completion.h> +#include <linux/kmemleak.h> #include <linux/moduleparam.h> #include <linux/panic.h> #include <linux/panic_notifier.h>
@@ -3389,6 +3390,14 @@ void kvfree_call_rcu(struct rcu_head *head, void *ptr) success = true; } +/* +* The kvfree_rcu() caller considers the pointer freed at this point +* and likely removes any references to it. Since the actual slab +* freeing (and kmemleak_free()) is deferred, tell kmemleak to ignore +* this object (no scanning or false positives reporting). +*/ +kmemleak_ignore (ptr); +// Set timer to drain after KFREE_DRAIN_JIFFIES. if (rcu_scheduler_active ==RCU_SCHEDULER_RUNNING )schedule_delayed_monitor_work (krcp);
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 11/18 rcu: Use rcu_segcblist_segempty() instead of open coding it
In reply to Frederic WeisbeckerThis makes the code more readable.
--- kernel/rcu/rcu_segcblist.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
kernel/rcu/rcu_segcblist.c +2 -2
@@ -368,7 +368,7 @@ bool rcu_segcblist_entrain(struct rcu_segcblist *rsclp,smp_mb ();/* Ensure counts are updated before callback is entrained. */ rhp->next =NULL ;for (i =RCU_NEXT_TAIL ; i >RCU_DONE_TAIL ; i--) -if (rsclp->tails[i] != rsclp->tails[i -1 ]) +if (!rcu_segcblist_segempty (rsclp, i))break ;rcu_segcblist_inc_seglen (rsclp, i);WRITE_ONCE (*rsclp->tails[i], rhp);
@@ -551,7 +551,7 @@ bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq)* as their ->gp_seq[] grace-period completion sequence number. */ for (i =RCU_NEXT_READY_TAIL ; i >RCU_DONE_TAIL ; i--) -if (rsclp->tails[i] != rsclp->tails[i -1 ] && +if (!rcu_segcblist_segempty (rsclp, i) &&ULONG_CMP_LT (rsclp->gp_seq[i], seq))break ;
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 12/18 rcu: Assume IRQS disabled from rcu_report_dead()
In reply to Frederic Weisbeckerrcu_report_dead() is the last RCU word from the CPU down through the hotplug path. It is called in the idle loop right before the CPU shuts down for good. Because it removes the CPU from the grace period state machine and reports an ultimate quiescent state if necessary, no further use of RCU is allowed. Therefore it is expected that IRQs are disabled upon calling this function and are not to be re-enabled again until the CPU shuts down.
Remove the IRQs disablement from that function and verify instead that it is actually called with IRQs disabled as it is expected at that special point in the idle path.
--- kernel/rcu/tree.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
kernel/rcu/tree.c +6 -4
@@ -4562,11 +4562,16 @@ void rcu_cpu_starting(unsigned int cpu)*/ void rcu_report_dead (unsigned int cpu) { -unsigned long flags, seq_flags; +unsigned long flags;unsigned long mask;struct rcu_data *rdp =per_cpu_ptr (&rcu_data, cpu);struct rcu_node *rnp = rdp->mynode;/* Outgoing CPU's rdp & rnp. */ +/* +* IRQS must be disabled from now on and until the CPU dies, or an interrupt +* may introduce a new READ-side while it is actually off the QS masks. +*/ +lockdep_assert_irqs_disabled ();// Do any dangling deferred wakeups. do_nocb_deferred_wakeup (rdp);
@@ -4574,7 +4579,6 @@ void rcu_report_dead(unsigned int cpu)/* Remove outgoing CPU from mask in the leaf rcu_node structure. */ mask = rdp->grpmask; -local_irq_save (seq_flags);arch_spin_lock (&rcu_state.ofl_lock);raw_spin_lock_irqsave_rcu_node (rnp, flags);/* Enforce GP memory-order guarantee. */ rdp->rcu_ofl_gp_seq =READ_ONCE (rcu_state.gp_seq);
@@ -4588,8 +4592,6 @@ void rcu_report_dead(unsigned int cpu)WRITE_ONCE (rnp->qsmaskinitnext, rnp->qsmaskinitnext & ~mask);raw_spin_unlock_irqrestore_rcu_node (rnp, flags);arch_spin_unlock (&rcu_state.ofl_lock); -local_irq_restore (seq_flags); - rdp->cpu_started = false; }
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 13/18 rcu: Assume rcu_report_dead() is always called locally
In reply to Frederic Weisbeckerrcu_report_dead() has to be called locally by the CPU that is going to exit the RCU state machine. Passing a cpu argument here is error-prone and leaves the possibility for a racy remote call.
Use local access instead.
--- arch/arm64/kernel/smp.c | 2 +- include/linux/rcupdate.h | 2 +- kernel/cpu.c | 2 +- kernel/rcu/tree.c | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-)
arch/arm64/kernel/smp.c +1 -1
@@ -401,7 +401,7 @@ void __noreturn cpu_die_early(void)/* Mark this CPU absent */ set_cpu_present (cpu,0 ); -rcu_report_dead (cpu); +rcu_report_dead ();if (IS_ENABLED (CONFIG_HOTPLUG_CPU )) {update_cpu_boot_status (CPU_KILL_ME );
include/linux/rcupdate.h +1 -1
@@ -122,7 +122,7 @@ static inline void call_rcu_hurry(struct rcu_head *head, rcu_callback_t func)void rcu_init (void );extern int rcu_scheduler_active;void rcu_sched_clock_irq (int user); -void rcu_report_dead (unsigned int cpu); +void rcu_report_dead (void );void rcutree_migrate_callbacks (int cpu);#ifdef CONFIG_TASKS_RCU_GENERIC
kernel/cpu.c +1 -1
@@ -1388,7 +1388,7 @@ void cpuhp_report_idle_dead(void)struct cpuhp_cpu_state *st =this_cpu_ptr (&cpuhp_state);BUG_ON (st->state !=CPUHP_AP_OFFLINE ); -rcu_report_dead (smp_processor_id ()); +rcu_report_dead (); st->state =CPUHP_AP_IDLE_DEAD ;/* * We cannot call complete after rcu_report_dead() so we delegate it
kernel/rcu/tree.c +2 -2
@@ -4560,11 +4560,11 @@ void rcu_cpu_starting(unsigned int cpu)* from the outgoing CPU rather than from the cpuhp_step mechanism. * This is because this function must be invoked at a precise location. */ -void rcu_report_dead (unsigned int cpu) +void rcu_report_dead (void ) {unsigned long flags;unsigned long mask; -struct rcu_data *rdp =per_cpu_ptr (&rcu_data, cpu); +struct rcu_data *rdp =this_cpu_ptr (&rcu_data);struct rcu_node *rnp = rdp->mynode;/* Outgoing CPU's rdp & rnp. */ /*
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 14/18 rcu: Conditionally build CPU-hotplug teardown callbacks
In reply to Frederic WeisbeckerAmong the three CPU-hotplug teardown RCU callbacks, two of them early exit if CONFIG_HOTPLUG_CPU=n, and one is left unchanged. In any case all of them have an implementation when CONFIG_HOTPLUG_CPU=n.
Align instead with the common way to deal with CPU-hotplug teardown callbacks and provide a proper stub when they are not supported.
--- include/linux/rcutree.h | 11 +++- kernel/rcu/tree.c | 114 +++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 62 deletions(-)
include/linux/rcutree.h +9 -2
@@ -110,9 +110,16 @@ void rcu_all_qs(void);/* RCUtree hotplug events */ int rcutree_prepare_cpu (unsigned int cpu);int rcutree_online_cpu (unsigned int cpu); -int rcutree_offline_cpu (unsigned int cpu); +void rcu_cpu_starting (unsigned int cpu); + +#ifdef CONFIG_HOTPLUG_CPU int rcutree_dead_cpu (unsigned int cpu);int rcutree_dying_cpu (unsigned int cpu); -void rcu_cpu_starting (unsigned int cpu); +int rcutree_offline_cpu (unsigned int cpu); +#else +#define rcutree_dead_cpu NULL +#define rcutree_dying_cpu NULL +#define rcutree_offline_cpu NULL +#endif #endif /* __LINUX_RCUTREE_H */
kernel/rcu/tree.c +54 -60
138 lines not on this page · raw message
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 15/18 rcu: Standardize explicit CPU-hotplug calls
In reply to Frederic Weisbeckerrcu_report_dead() and rcutree_migrate_callbacks() have their headers in rcupdate.h while those are pure rcutree calls, like the other CPU-hotplug functions.
Also rcu_cpu_starting() and rcu_report_dead() have different naming conventions while they mirror each other's effects.
Fix the headers and propose a naming that relates both functions and aligns with the prefix of other rcutree CPU-hotplug functions.
--- .../Expedited-Grace-Periods.rst | 2 +- .../RCU/Design/Memory-Ordering/TreeRCU-gp-fqs.svg | 4 ++-- .../RCU/Design/Memory-Ordering/TreeRCU-gp.svg | 4 ++-- .../RCU/Design/Memory-Ordering/TreeRCU-hotplug.svg | 4 ++-- .../RCU/Design/Requirements/Requirements.rst | 4 ++-- arch/arm64/kernel/smp.c | 4 ++-- arch/powerpc/kernel/smp.c | 2 +- arch/s390/kernel/smp.c | 2 +- arch/x86/kernel/smpboot.c | 2 +- include/linux/interrupt.h | 2 +- include/linux/rcupdate.h | 2 -- include/linux/rcutiny.h | 2 +- include/linux/rcutree.h | 7 ++++++- kernel/cpu.c | 6 +++--- kernel/rcu/tree.c | 12 ++++++++---- 15 files changed, 33 insertions(+), 26 deletions(-)
Documentation/RCU/Design/Expedited-Grace-Periods/Expedited-Grace-Periods.rst +1 -1
@@ -181,7 +181,7 @@ operations is carried out at several levels: of this wait (or series of waits, as the case may be) is to permit a concurrent CPU-hotplug operation to complete. #. In the case of RCU-sched, one of the last acts of an outgoing CPU is - to invoke ``rcu_report_dead()``, which reports a quiescent state for + to invoke ``rcutree_report_cpu_dead()``, which reports a quiescent state for that CPU. However, this is likely paranoia-induced redundancy. +-----------------------------------------------------------------------+
Documentation/RCU/Design/Memory-Ordering/TreeRCU-gp-fqs.svg +2 -2
@@ -1135,7 +1135,7 @@ font-weight="bold" font-size="192" id="text202-7-5-3-27-6-5" - style="font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-family:Courier">rcu_report_dead()</text> + style="font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-family:Courier">rcutree_report_cpu_dead()</text> <text xml:space="preserve" x="3745.7725"
@@ -1256,7 +1256,7 @@ font-style="normal" y="3679.27" x="-3804.9949" - xml:space="preserve">rcu_cpu_starting()</text> + xml:space="preserve">rcutree_report_cpu_starting()</text> <g style="fill:none;stroke-width:0.025in" id="g3107-7-5-0"
Documentation/RCU/Design/Memory-Ordering/TreeRCU-gp.svg +2 -2
@@ -3274,7 +3274,7 @@ font-weight="bold" font-size="192" id="text202-7-5-3-27-6-5" - style="font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-family:Courier">rcu_report_dead()</text> + style="font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-family:Courier">rcutree_report_cpu_dead()</text> <text xml:space="preserve" x="3745.7725"
@@ -3395,7 +3395,7 @@ font-style="normal" y="3679.27" x="-3804.9949" - xml:space="preserve">rcu_cpu_starting()</text> + xml:space="preserve">rcutree_report_cpu_starting()</text> <g style="fill:none;stroke-width:0.025in" id="g3107-7-5-0"
Documentation/RCU/Design/Memory-Ordering/TreeRCU-hotplug.svg +2 -2
@@ -607,7 +607,7 @@ font-weight="bold" font-size="192" id="text202-7-5-3-27-6" - style="font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-family:Courier">rcu_report_dead()</text> + style="font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-family:Courier">rcutree_report_cpu_dead()</text> <text xml:space="preserve" x="3745.7725"
@@ -728,7 +728,7 @@ font-style="normal" y="3679.27" x="-3804.9949" - xml:space="preserve">rcu_cpu_starting()</text> + xml:space="preserve">rcutree_report_cpu_starting()</text> <g style="fill:none;stroke-width:0.025in" id="g3107-7-5-0"
Documentation/RCU/Design/Requirements/Requirements.rst +2 -2
@@ -1955,12 +1955,12 @@ if offline CPUs block an RCU grace period for too long. An offline CPU's quiescent state will be reported either: -1. As the CPU goes offline using RCU's hotplug notifier (rcu_report_dead()). +1. As the CPU goes offline using RCU's hotplug notifier (rcutree_report_cpu_dead()). 2. When grace period initialization (rcu_gp_init()) detects a race either with CPU offlining or with a task unblocking on a leaf ``rcu_node`` structure whose CPUs are all offline. -The CPU-online path (rcu_cpu_starting()) should never need to report +The CPU-online path (rcutree_report_cpu_starting()) should never need to report a quiescent state for an offline CPU. However, as a debugging measure, it does emit a warning if a quiescent state was not already reported for that CPU.
arch/arm64/kernel/smp.c +2 -2
@@ -215,7 +215,7 @@ asmlinkage notrace void secondary_start_kernel(void)if (system_uses_irq_prio_masking ())init_gic_priority_masking (); -rcu_cpu_starting (cpu); +rcutree_report_cpu_starting (cpu);trace_hardirqs_off ();/*
@@ -401,7 +401,7 @@ void __noreturn cpu_die_early(void)/* Mark this CPU absent */ set_cpu_present (cpu,0 ); -rcu_report_dead (); +rcutree_report_cpu_dead ();if (IS_ENABLED (CONFIG_HOTPLUG_CPU )) {update_cpu_boot_status (CPU_KILL_ME );
arch/powerpc/kernel/smp.c +1 -1
@@ -1629,7 +1629,7 @@ void start_secondary(void *unused)smp_store_cpu_info (cpu);set_dec (tb_ticks_per_jiffy); -rcu_cpu_starting (cpu); +rcutree_report_cpu_starting (cpu); cpu_callin_map[cpu] =1 ;if (smp_ops->setup_cpu)
arch/s390/kernel/smp.c +1 -1
@@ -898,7 +898,7 @@ static void smp_start_secondary(void *cpuvoid) S390_lowcore.restart_flags =0 ;restore_access_regs (S390_lowcore.access_regs_save_area);cpu_init (); -rcu_cpu_starting (cpu); +rcutree_report_cpu_starting (cpu);init_cpu_timer ();vtime_init ();vdso_getcpu_init ();
arch/x86/kernel/smpboot.c +1 -1
@@ -288,7 +288,7 @@ static void notrace start_secondary(void *unused)cpu_init ();fpu__init_cpu (); -rcu_cpu_starting (raw_smp_processor_id ()); +rcutree_report_cpu_starting (raw_smp_processor_id ()); x86_cpuinit.early_percpu_clock_init ();ap_starting ();
include/linux/interrupt.h +1 -1
@@ -566,7 +566,7 @@ enum* * _ RCU: * 1) rcutree_migrate_callbacks() migrates the queue. -* 2) rcu_report_dead() reports the final quiescent states. +* 2) rcutree_report_cpu_dead() reports the final quiescent states. * * _ IRQ_POLL: irq_poll_cpu_dead() migrates the queue */
include/linux/rcupdate.h +0 -2
@@ -122,8 +122,6 @@ static inline void call_rcu_hurry(struct rcu_head *head, rcu_callback_t func)void rcu_init (void );extern int rcu_scheduler_active;void rcu_sched_clock_irq (int user); -void rcu_report_dead (void ); -void rcutree_migrate_callbacks (int cpu);#ifdef CONFIG_TASKS_RCU_GENERIC void rcu_init_tasks_generic (void );
include/linux/rcutiny.h +1 -1
@@ -171,6 +171,6 @@ static inline void rcu_all_qs(void) { barrier(); }#define rcutree_offline_cpu NULL#define rcutree_dead_cpu NULL#define rcutree_dying_cpu NULL -static inline void rcu_cpu_starting (unsigned int cpu) { } +static inline void rcutree_report_cpu_starting (unsigned int cpu) { }#endif /* __LINUX_RCUTINY_H */
include/linux/rcutree.h +6 -1
@@ -110,7 +110,7 @@ void rcu_all_qs(void);/* RCUtree hotplug events */ int rcutree_prepare_cpu (unsigned int cpu);int rcutree_online_cpu (unsigned int cpu); -void rcu_cpu_starting (unsigned int cpu); +void rcutree_report_cpu_starting (unsigned int cpu);#ifdef CONFIG_HOTPLUG_CPU int rcutree_dead_cpu (unsigned int cpu);
@@ -122,4 +122,9 @@ int rcutree_offline_cpu(unsigned int cpu);#define rcutree_offline_cpu NULL#endif +void rcutree_migrate_callbacks (int cpu); + +/* Called from hotplug and also arm64 early secondary boot failure */ +void rcutree_report_cpu_dead (void ); +#endif /* __LINUX_RCUTREE_H */
kernel/cpu.c +3 -3
@@ -1388,10 +1388,10 @@ void cpuhp_report_idle_dead(void)struct cpuhp_cpu_state *st =this_cpu_ptr (&cpuhp_state);BUG_ON (st->state !=CPUHP_AP_OFFLINE ); -rcu_report_dead (); +rcutree_report_cpu_dead (); st->state =CPUHP_AP_IDLE_DEAD ;/* -* We cannot call complete after rcu_report_dead() so we delegate it +* We cannot call complete after rcutree_report_cpu_dead() so we delegate it * to an online cpu. */ smp_call_function_single(cpumask_first(cpu_online_mask),
@@ -1617,7 +1617,7 @@ void notify_cpu_starting(unsigned int cpu)struct cpuhp_cpu_state *st =per_cpu_ptr (&cpuhp_state, cpu);enum cpuhp_state target =min ((int )st->target,CPUHP_AP_ONLINE ); -rcu_cpu_starting (cpu);/* Enables RCU usage on this CPU. */ +rcutree_report_cpu_starting (cpu);/* Enables RCU usage on this CPU. */ cpumask_set_cpu (cpu, &cpus_booted_once_mask);/*
kernel/rcu/tree.c +8 -4
@@ -4216,7 +4216,7 @@ bool rcu_lockdep_current_cpu_online(void) rdp =this_cpu_ptr (&rcu_data);/* * Strictly, we care here about the case where the current CPU is -* in rcu_cpu_starting() and thus has an excuse for rdp->grpmask +* in rcutree_report_cpu_starting() and thus has an excuse for rdp->grpmask * not being up to date. So arch_spin_is_locked() might have a * false positive if it's held by some *other* CPU, but that's * OK because that just means a false *negative* on the warning.
@@ -4445,8 +4445,10 @@ int rcutree_online_cpu(unsigned int cpu)* from the incoming CPU rather than from the cpuhp_step mechanism. * This is because this function must be invoked at a precise location. * This incoming CPU must not have enabled interrupts yet. +* +* This mirrors the effects of rcutree_report_cpu_dead(). */ -void rcu_cpu_starting (unsigned int cpu) +void rcutree_report_cpu_starting (unsigned int cpu) {unsigned long mask;struct rcu_data *rdp;
@@ -4500,8 +4502,10 @@ void rcu_cpu_starting(unsigned int cpu)* Note that this function is special in that it is invoked directly * from the outgoing CPU rather than from the cpuhp_step mechanism. * This is because this function must be invoked at a precise location. +* +* This mirrors the effect of rcutree_report_cpu_starting(). */ -void rcu_report_dead (void ) +void rcutree_report_cpu_dead (void ) {unsigned long flags;unsigned long mask;
@@ -5072,7 +5076,7 @@ void __init rcu_init(void)pm_notifier (rcu_pm_notify,0 );WARN_ON (num_online_cpus () >1 );// Only one CPU this early in boot. rcutree_prepare_cpu (cpu); -rcu_cpu_starting (cpu); +rcutree_report_cpu_starting (cpu);rcutree_online_cpu (cpu);/* Create workqueue for Tree SRCU and for expedited GPs. */
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 16/18 rcu: Comment why callbacks migration can't wait for CPUHP_RCUTREE_PREP
In reply to Frederic WeisbeckerThe callbacks migration is performed through an explicit call from the hotplug control CPU right after the death of the target CPU and before proceeding with the CPUHP_ teardown functions.
This is unusual but necessary and yet uncommented. Summarize the reason as explained in the changelog of:
a58163d8ca2c (rcu: Migrate callbacks earlier in the CPU-offline timeline)
--- kernel/cpu.c | 7 +++++++ 1 file changed, 7 insertions(+)
kernel/cpu.c +7 -0
@@ -1372,7 +1372,14 @@ static int takedown_cpu(unsigned int cpu)cpuhp_bp_sync_dead (cpu);tick_cleanup_dead_cpu (cpu); + +/* +* Callbacks must be re-integrated right away to the RCU state machine. +* Otherwise an RCU callback could block a further teardown function +* waiting for its completion. +*/ rcutree_migrate_callbacks (cpu); +return 0 ; }
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 17/18 srcu: Fix callbacks acceleration mishandling
In reply to Frederic WeisbeckerSRCU callbacks acceleration might fail if the preceding callbacks advance also fails. This can happen when the following steps are met:
1) The RCU_WAIT_TAIL segment has callbacks (say for gp_num 8) and the RCU_NEXT_READY_TAIL also has callbacks (say for gp_num 12).
2) The grace period for RCU_WAIT_TAIL is observed as started but not yet completed so rcu_seq_current() returns 4 + SRCU_STATE_SCAN1 = 5.
3) This value is passed to rcu_segcblist_advance() which can't move any segment forward and fails.
4) srcu_gp_start_if_needed() still proceeds with callback acceleration. But then the call to rcu_seq_snap() observes the grace period for the RCU_WAIT_TAIL segment (gp_num 8) as completed and the subsequent one for the RCU_NEXT_READY_TAIL segment as started
(ie: 8 + SRCU_STATE_SCAN1 = 9) so it returns a snapshot of the next grace period, which is 16.
5) The value of 16 is passed to rcu_segcblist_accelerate() but the freshly enqueued callback in RCU_NEXT_TAIL can't move to RCU_NEXT_READY_TAIL which already has callbacks for a previous grace period (gp_num = 12). So acceleration fails.
6) Note in all these steps, srcu_invoke_callbacks() hadn't had a chance to run srcu_invoke_callbacks().
Then some very bad outcome may happen if the following happens:
7) Some other CPU races and starts the grace period number 16 before the CPU handling previous steps had a chance. Therefore srcu_gp_start() isn't called on the latter sdp to fix the acceleration leak from previous steps with a new pair of call to advance/accelerate.
8) The grace period 16 completes and srcu_invoke_callbacks() is finally called. All the callbacks from previous grace periods (8 and 12) are correctly advanced and executed but callbacks in RCU_NEXT_READY_TAIL still remain. Then rcu_segcblist_accelerate() is called with a snaphot of 20.
9) Since nothing started the grace period number 20, callbacks stay unhandled.
This has been reported in real load:
[3144162.608392] INFO: task kworker/136:12:252684 blocked for more than 122 seconds. [3144162.615986] Tainted: G O K 5.4.203-1-tlinux4-0011.1 #1 [3144162.623053] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. [3144162.631162] kworker/136:12 D 0 252684 2 0x90004000 [3144162.631189] Workqueue: kvm-irqfd-cleanup irqfd_shutdown [kvm] [3144162.631192] Call Trace: [3144162.631202] __schedule+0x2ee/0x660 [3144162.631206] schedule+0x33/0xa0 [3144162.631209] schedule_timeout+0x1c4/0x340 [3144162.631214] ? update_load_avg+0x82/0x660 [3144162.631217] ? raw_spin_rq_lock_nested+0x1f/0x30 [3144162.631218] wait_for_completion+0x119/0x180 [3144162.631220] ? wake_up_q+0x80/0x80 [3144162.631224] __synchronize_srcu.part.19+0x81/0xb0 [3144162.631226] ? __bpf_trace_rcu_utilization+0x10/0x10 [3144162.631227] synchronize_srcu+0x5f/0xc0 [3144162.631236] irqfd_shutdown+0x3c/0xb0 [kvm] [3144162.631239] ? __schedule+0x2f6/0x660 [3144162.631243] process_one_work+0x19a/0x3a0 [3144162.631244] worker_thread+0x37/0x3a0 [3144162.631247] kthread+0x117/0x140 [3144162.631247] ? process_one_work+0x3a0/0x3a0 [3144162.631248] ? __kthread_cancel_work+0x40/0x40 [3144162.631250] ret_from_fork+0x1f/0x30
Fix this with taking the snapshot for acceleration _before_ the read of the current grace period number.
The only side effect of this solution is that callbacks advancing happen then _after_ the full barrier in rcu_seq_snap(). This is not a problem because that barrier only cares about:
1) Ordering accesses of the update side before call_srcu() so they don't bleed.
2) See all the accesses prior to the grace period of the current gp_num
The only things callbacks advancing need to be ordered against are carried by snp locking.
--- kernel/rcu/srcutree.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-)
kernel/rcu/srcutree.c +30 -3
@@ -1244,10 +1244,37 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp,spin_lock_irqsave_sdp_contention (sdp, &flags);if (rhp)rcu_segcblist_enqueue (&sdp->srcu_cblist, rhp); -rcu_segcblist_advance (&sdp->srcu_cblist, -rcu_seq_current (&ssp->srcu_sup->srcu_gp_seq)); +/* +* The snapshot for acceleration must be taken _before_ the read of the +* current gp sequence used for advancing, otherwise advancing may fail +* and acceleration may then fail too. +* +* This could happen if: +* +* 1) The RCU_WAIT_TAIL segment has callbacks (gp_num = X + 4) and the +* RCU_NEXT_READY_TAIL also has callbacks (gp_num = X + 8). +* +* 2) The grace period for RCU_WAIT_TAIL is seen as started but not +* completed so rcu_seq_current() returns X + SRCU_STATE_SCAN1. +* +* 3) This value is passed to rcu_segcblist_advance() which can't move +* any segment forward and fails. +* +* 4) srcu_gp_start_if_needed() still proceeds with callback acceleration. +* But then the call to rcu_seq_snap() observes the grace period for the +* RCU_WAIT_TAIL segment as completed and the subsequent one for the +* RCU_NEXT_READY_TAIL segment as started (ie: X + 4 + SRCU_STATE_SCAN1) +* so it returns a snapshot of the next grace period, which is X + 12. +* +* 5) The value of X + 12 is passed to rcu_segcblist_accelerate() but the +* freshly enqueued callback in RCU_NEXT_TAIL can't move to +* RCU_NEXT_READY_TAIL which already has callbacks for a previous grace +* period (gp_num = X + 8). So acceleration fails. +*/ s =rcu_seq_snap (&ssp->srcu_sup->srcu_gp_seq); - (void )rcu_segcblist_accelerate (&sdp->srcu_cblist, s); +rcu_segcblist_advance (&sdp->srcu_cblist, +rcu_seq_current (&ssp->srcu_sup->srcu_gp_seq)); +WARN_ON_ONCE (!rcu_segcblist_accelerate (&sdp->srcu_cblist, s) && rhp);if (ULONG_CMP_LT (sdp->srcu_gp_seq_needed, s)) { sdp->srcu_gp_seq_needed = s; needgp = true;
Signature · 1 line
2.34.1
Frederic Weisbecker
maintainerPATCH 18/18 srcu: Only accelerate on enqueue time
In reply to Frederic WeisbeckerAcceleration in SRCU happens on enqueue time for each new callback. This operation is expected not to fail and therefore any similar attempt from other places shouldn't find any remaining callbacks to accelerate.
Moreover accelerations performed beyond enqueue time are error prone because rcu_seq_snap() then may return the snapshot for a new grace period that is not going to be started.
Remove these dangerous and needless accelerations and introduce instead assertions reporting leaking unaccelerated callbacks beyond enqueue time.
--- kernel/rcu/srcutree.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
kernel/rcu/srcutree.c +2 -4
@@ -784,8 +784,7 @@ static void srcu_gp_start(struct srcu_struct *ssp)spin_lock_rcu_node (sdp);/* Interrupts already disabled. */ rcu_segcblist_advance (&sdp->srcu_cblist,rcu_seq_current (&ssp->srcu_sup->srcu_gp_seq)); - (void )rcu_segcblist_accelerate (&sdp->srcu_cblist, -rcu_seq_snap (&ssp->srcu_sup->srcu_gp_seq)); +WARN_ON_ONCE (!rcu_segcblist_segempty (&sdp->srcu_cblist,RCU_NEXT_TAIL ));spin_unlock_rcu_node (sdp);/* Interrupts remain disabled. */ WRITE_ONCE (ssp->srcu_sup->srcu_gp_start, jiffies);WRITE_ONCE (ssp->srcu_sup->srcu_n_exp_nodelay,0 );
@@ -1721,6 +1720,7 @@ static void srcu_invoke_callbacks(struct work_struct *work) ssp = sdp->ssp;rcu_cblist_init (&ready_cbs);spin_lock_irq_rcu_node (sdp); +WARN_ON_ONCE (!rcu_segcblist_segempty (&sdp->srcu_cblist,RCU_NEXT_TAIL ));rcu_segcblist_advance (&sdp->srcu_cblist,rcu_seq_current (&ssp->srcu_sup->srcu_gp_seq)); if (sdp->srcu_cblist_invoking ||
@@ -1750,8 +1750,6 @@ static void srcu_invoke_callbacks(struct work_struct *work)*/ spin_lock_irq_rcu_node (sdp);rcu_segcblist_add_len (&sdp->srcu_cblist, -len); - (void )rcu_segcblist_accelerate (&sdp->srcu_cblist, -rcu_seq_snap (&ssp->srcu_sup->srcu_gp_seq)); sdp->srcu_cblist_invoking = false; more =rcu_segcblist_ready_cbs (&sdp->srcu_cblist);spin_unlock_irq_rcu_node (sdp);
Signature · 1 line
2.34.1