Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

rcu/nocb: Disable bypass when CPU isn't completely offloaded

Currently, the bypass is flushed at the very last moment in the
deoffloading procedure. However, this approach leads to a larger state
space than would be preferred. This commit therefore disables the
bypass at soon as the deoffloading procedure begins, then flushes it.
This guarantees that the bypass remains empty and thus out of the way
of the deoffloading procedure.

Symmetrically, this commit waits to enable the bypass until the offloading
procedure has completed.

Reported-by: Paul E. McKenney <paulmck@kernel.org>
Cc: Josh Triplett <josh@joshtriplett.org>
Cc: Lai Jiangshan <jiangshanlai@gmail.com>
Cc: Joel Fernandes <joel@joelfernandes.org>
Cc: Neeraj Upadhyay <neeraju@codeaurora.org>
Cc: Boqun Feng <boqun.feng@gmail.com>
Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>

authored by

Frederic Weisbecker and committed by
Paul E. McKenney
76d00b49 b2fcf210

+33 -12
+4 -3
include/linux/rcu_segcblist.h
··· 109 109 * | SEGCBLIST_KTHREAD_GP | 110 110 * | | 111 111 * | Kthreads handle callbacks holding nocb_lock, local rcu_core() stops | 112 - * | handling callbacks. | 112 + * | handling callbacks. Enable bypass queueing. | 113 113 * ---------------------------------------------------------------------------- 114 114 */ 115 115 ··· 125 125 * | SEGCBLIST_KTHREAD_GP | 126 126 * | | 127 127 * | CB/GP kthreads handle callbacks holding nocb_lock, local rcu_core() | 128 - * | ignores callbacks. | 128 + * | ignores callbacks. Bypass enqueue is enabled. | 129 129 * ---------------------------------------------------------------------------- 130 130 * | 131 131 * v ··· 134 134 * | SEGCBLIST_KTHREAD_GP | 135 135 * | | 136 136 * | CB/GP kthreads and local rcu_core() handle callbacks concurrently | 137 - * | holding nocb_lock. Wake up CB and GP kthreads if necessary. | 137 + * | holding nocb_lock. Wake up CB and GP kthreads if necessary. Disable | 138 + * | bypass enqueue. | 138 139 * ---------------------------------------------------------------------------- 139 140 * | 140 141 * v
+29 -9
kernel/rcu/tree_plugin.h
··· 1830 1830 unsigned long j = jiffies; 1831 1831 long ncbs = rcu_cblist_n_cbs(&rdp->nocb_bypass); 1832 1832 1833 + lockdep_assert_irqs_disabled(); 1834 + 1835 + // Pure softirq/rcuc based processing: no bypassing, no 1836 + // locking. 1833 1837 if (!rcu_rdp_is_offloaded(rdp)) { 1838 + *was_alldone = !rcu_segcblist_pend_cbs(&rdp->cblist); 1839 + return false; 1840 + } 1841 + 1842 + // In the process of (de-)offloading: no bypassing, but 1843 + // locking. 1844 + if (!rcu_segcblist_completely_offloaded(&rdp->cblist)) { 1845 + rcu_nocb_lock(rdp); 1834 1846 *was_alldone = !rcu_segcblist_pend_cbs(&rdp->cblist); 1835 1847 return false; /* Not offloaded, no bypassing. */ 1836 1848 } 1837 - lockdep_assert_irqs_disabled(); 1838 1849 1839 1850 // Don't use ->nocb_bypass during early boot. 1840 1851 if (rcu_scheduler_active != RCU_SCHEDULER_RUNNING) { ··· 2427 2416 pr_info("De-offloading %d\n", rdp->cpu); 2428 2417 2429 2418 rcu_nocb_lock_irqsave(rdp, flags); 2430 - 2419 + /* 2420 + * Flush once and for all now. This suffices because we are 2421 + * running on the target CPU holding ->nocb_lock (thus having 2422 + * interrupts disabled), and because rdp_offload_toggle() 2423 + * invokes rcu_segcblist_offload(), which clears SEGCBLIST_OFFLOADED. 2424 + * Thus future calls to rcu_segcblist_completely_offloaded() will 2425 + * return false, which means that future calls to rcu_nocb_try_bypass() 2426 + * will refuse to put anything into the bypass. 2427 + */ 2428 + WARN_ON_ONCE(!rcu_nocb_flush_bypass(rdp, NULL, jiffies)); 2431 2429 ret = rdp_offload_toggle(rdp, false, flags); 2432 2430 swait_event_exclusive(rdp->nocb_state_wq, 2433 2431 !rcu_segcblist_test_flags(cblist, SEGCBLIST_KTHREAD_CB | ··· 2448 2428 del_timer_sync(&rdp->nocb_timer); 2449 2429 2450 2430 /* 2451 - * Flush bypass. While IRQs are disabled and once we set 2452 - * SEGCBLIST_SOFTIRQ_ONLY, no callback is supposed to be 2453 - * enqueued on bypass. 2431 + * Theoretically we could set SEGCBLIST_SOFTIRQ_ONLY with CB unlocked 2432 + * and IRQs disabled but let's be paranoid. 2454 2433 */ 2455 2434 rcu_nocb_lock_irqsave(rdp, flags); 2456 - rcu_nocb_flush_bypass(rdp, NULL, jiffies); 2457 2435 rcu_segcblist_set_flags(cblist, SEGCBLIST_SOFTIRQ_ONLY); 2458 2436 /* 2459 2437 * With SEGCBLIST_SOFTIRQ_ONLY, we can't use 2460 - * rcu_nocb_unlock_irqrestore() anymore. Theoretically we 2461 - * could set SEGCBLIST_SOFTIRQ_ONLY with cb unlocked and IRQs 2462 - * disabled now, but let's be paranoid. 2438 + * rcu_nocb_unlock_irqrestore() anymore. 2463 2439 */ 2464 2440 raw_spin_unlock_irqrestore(&rdp->nocb_lock, flags); 2441 + 2442 + /* Sanity check */ 2443 + WARN_ON_ONCE(rcu_cblist_n_cbs(&rdp->nocb_bypass)); 2444 + 2465 2445 2466 2446 return ret; 2467 2447 }