Patch to introduce RCU-bh QS where safe from softirq
Hello!
The below patch is a first cut for inserting RCU-bh quiescent states between successive calls to softirq handlers. This applies on top of rcu-disable-the-rcu-bh-stuff-for-rt.patch, which implements RCU-bh in terms of RCU-preempt when CONFIG_PREEMPT_RT_FULL=y.
This patch is probably totally broken, so RFC only.
So, on to scripting running -rt under KVM...
Thanx, Paul
------------------------------------------------------------------------
rcu: Make ksoftirqd do RCU quiescent states
Implementing RCU-bh in terms of RCU-preempt makes the system vulnerable to network-based denial-of-service attacks. This patch therefore makes __do_softirq() invoke rcu_bh_qs(), but only when __do_softirq() is running in ksoftirqd context. A wrapper layer in interposed so that other calls to __do_softirq() avoid invoking rcu_bh_qs(). The underlying function __do_softirq_common() does the actual work.
The reason that rcu_bh_qs() is bad in these non-ksoftirqd contexts is that there might be a local_bh_enable() inside an RCU-preempt read-side critical section. This local_bh_enable() can invoke __do_softirq() directly, so if __do_softirq() were to invoke rcu_bh_qs() (which just calls rcu_preempt_qs() in the PREEMPT_RT_FULL case), there would be an illegal RCU-preempt quiescent state in the middle of an RCU-preempt read-side critical section. Therefore, quiescent states can only happen in cases where __do_softirq() is invoked directly from ksoftirqd.
include/linux/rcupdate.h +0 -6
@@ -144,13 +144,7 @@ static inline int rcu_preempt_depth(void)/* Internal to kernel */ extern void rcu_sched_qs (int cpu); - -#ifndef CONFIG_PREEMPT_RT_FULL extern void rcu_bh_qs (int cpu); -#else -static inline void rcu_bh_qs (int cpu) { } -#endif -extern void rcu_check_callbacks (int cpu,int user);struct notifier_block ;
kernel/rcutree.c +6 -1
@@ -166,7 +166,12 @@ void rcu_sched_qs(int cpu) rdp->passed_quiesc =1 ; } -#ifndef CONFIG_PREEMPT_RT_FULL +#ifdef CONFIG_PREEMPT_RT_FULL +void rcu_bh_qs (int cpu) +{ +rcu_preempt_qs (cpu); +} +#else void rcu_bh_qs (int cpu) {struct rcu_data *rdp = &per_cpu (rcu_bh_data, cpu);
kernel/rcutree.h +1 -0
@@ -422,6 +422,7 @@ DECLARE_PER_CPU(struct rcu_data, rcu_preempt_data);/* Forward declarations for rcutree_plugin.h */ static void rcu_bootup_announce (void );long rcu_batches_completed (void ); +static void rcu_preempt_qs (int cpu);static void rcu_preempt_note_context_switch (int cpu);static int rcu_preempt_blocked_readers_cgp (struct rcu_node *rnp);#ifdef CONFIG_HOTPLUG_CPU
kernel/rcutree_plugin.h +1 -1
@@ -1892,7 +1892,7 @@ EXPORT_SYMBOL_GPL(synchronize_sched_expedited);#endif /* #else #ifndef CONFIG_SMP */ -#if !defined(CONFIG_RCU_FAST_NO_HZ ) +#if 1 /* !defined(CONFIG_RCU_FAST_NO_HZ) */ /* * Check to see if any future RCU-related work will need to be done
kernel/softirq.c +13 -7
@@ -138,7 +138,7 @@ static void wakeup_softirqd(void)wake_up_process (tsk); } -static void handle_pending_softirqs (u32 pending,int cpu) +static void handle_pending_softirqs (u32 pending,int cpu,int need_rcu_bh_qs) {struct softirq_action *h = softirq_vec;unsigned int prev_count =preempt_count ();
@@ -161,7 +161,8 @@ static void handle_pending_softirqs(u32 pending, int cpu) prev_count, (unsigned int) preempt_count());preempt_count () = prev_count; } -rcu_bh_qs (cpu); +if (need_rcu_bh_qs) +rcu_bh_qs (cpu); }local_irq_disable (); }
@@ -313,7 +314,7 @@ restart:/* Reset the pending bitmask before enabling irqs */ set_softirq_pending (0 ); -handle_pending_softirqs (pending, cpu); +handle_pending_softirqs (pending, cpu,1 ); pending =local_softirq_pending ();if (pending && --max_restart)
@@ -383,7 +384,12 @@ static inline void ksoftirqd_clr_sched_params(void) { }static DEFINE_LOCAL_IRQ_LOCK (local_softirq_lock); static DEFINE_PER_CPU(struct task_struct *, local_softirq_runner); -static void __do_softirq (void ); +static void __do_softirq_common (int need_rcu_bh_qs); + +void __do_softirq (void ) +{ +__do_softirq_common (0 ); +}void __init softirq_early_init (void ) {
@@ -446,7 +452,7 @@ int in_serving_softirq(void)* Called with bh and local interrupts disabled. For full RT cpu must * be pinned. */ -static void __do_softirq (void ) +static void __do_softirq_common (int need_rcu_bh_qs) {u32 pending =local_softirq_pending ();int cpu =smp_processor_id ();
@@ -460,7 +466,7 @@ static void __do_softirq(void)lockdep_softirq_enter (); -handle_pending_softirqs (pending, cpu); +handle_pending_softirqs (pending, cpu, need_rcu_bh_qs); pending =local_softirq_pending ();if (pending)
@@ -499,7 +505,7 @@ static int __thread_do_softirq(int cpu)* schedule! */ if (local_softirq_pending ()) -__do_softirq (); +__do_softirq_common (cpu >=0 );local_unlock (local_softirq_lock);unpin_current_cpu ();preempt_disable ();
Quoted 10 lines · Paul E. McKenney
Hello!
The below patch is a first cut for inserting RCU-bh quiescent states between successive calls to softirq handlers. This applies on top of rcu-disable-the-rcu-bh-stuff-for-rt.patch, which implements RCU-bh in terms of RCU-preempt when
CONFIG_PREEMPT_RT_FULL=y.This patch is probably totally broken, so RFC only.
So, on to scripting running -rt under KVM...
And here is a version of rcu-disable-the-rcu-bh-stuff-for-rt.patch with an updated changelog.
Thanx, Paul
------------------------------------------------------------------------
rcu: Merge RCU-bh into RCU-preempt
The Linux kernel has long RCU-bh read-side critical sections that intolerably increase scheduling latency under mainline's RCU-bh rules, which include RCU-bh read-side critical sections being non-preemptible. This patch therefore arranges for RCU-bh to be implemented in terms of RCU-preempt for CONFIG_PREEMPT_RT_FULL=y.
This has the downside of defeating the purpose of RCU-bh, namely, handling the case where the system is subjected to a network-based denial-of-service attack that keeps at least one CPU doing full-time softirq processing. This issue will be fixed by a later commit.
The current commit will need some work to make it appropriate for mainline use, for example, it needs to be extended to cover Tiny RCU.
include/linux/rcupdate.h +32 -0
@@ -78,7 +78,13 @@ struct rcu_head {extern void call_rcu_sched (struct rcu_head *head,void (*func)(struct rcu_head *rcu));extern void synchronize_sched (void ); + +#ifdef CONFIG_PREEMPT_RT_FULL +# define rcu_barrier_bh rcu_barrier +#else extern void rcu_barrier_bh (void ); +#endif +extern void rcu_barrier_sched (void );static inline void __rcu_read_lock_bh (void )
@@ -138,7 +144,13 @@ static inline int rcu_preempt_depth(void)/* Internal to kernel */ extern void rcu_sched_qs (int cpu); + +#ifndef CONFIG_PREEMPT_RT_FULL extern void rcu_bh_qs (int cpu); +#else +static inline void rcu_bh_qs (int cpu) { } +#endif +extern void rcu_check_callbacks (int cpu,int user);struct notifier_block ;
@@ -229,7 +241,14 @@ static inline int rcu_read_lock_held(void)* rcu_read_lock_bh_held() is defined out of line to avoid #include-file * hell. */ +#ifdef CONFIG_PREEMPT_RT_FULL +static inline int rcu_read_lock_bh_held (void ) +{ +return rcu_read_lock_held (); +} +#else extern int rcu_read_lock_bh_held (void ); +#endif /** * rcu_read_lock_sched_held() - might we be in RCU-sched read-side critical section?
@@ -638,8 +657,13 @@ static inline void rcu_read_unlock(void)static inline void rcu_read_lock_bh (void ) {__rcu_read_lock_bh (); + +#ifdef CONFIG_PREEMPT_RT_FULL +rcu_read_lock (); +#else __acquire (RCU_BH );rcu_read_acquire_bh (); +#endif }/*
@@ -649,8 +673,12 @@ static inline void rcu_read_lock_bh(void)*/ static inline void rcu_read_unlock_bh (void ) { +#ifdef CONFIG_PREEMPT_RT_FULL +rcu_read_unlock (); +#else rcu_read_release_bh ();__release (RCU_BH ); +#endif __rcu_read_unlock_bh (); }
@@ -757,6 +785,9 @@ extern void call_rcu(struct rcu_head *head,#endif /* #else #ifdef CONFIG_PREEMPT_RCU */ +#ifdef CONFIG_PREEMPT_RT_FULL +#define call_rcu_bh call_rcu +#else /** * call_rcu_bh() - Queue an RCU for invocation after a quicker grace period. * @head: structure to be used for queueing the RCU updates.
@@ -777,6 +808,7 @@ extern void call_rcu(struct rcu_head *head,*/ extern void call_rcu_bh (struct rcu_head *head,void (*func)(struct rcu_head *head)); +#endif /* * debug_rcu_head_queue()/debug_rcu_head_unqueue() are used internally
include/linux/rcutree.h +12 -2
@@ -57,7 +57,11 @@ static inline void exit_rcu(void)#endif /* #else #ifdef CONFIG_TREE_PREEMPT_RCU */ +#ifndef CONFIG_PREEMPT_RT_FULL extern void synchronize_rcu_bh (void ); +#else +# define synchronize_rcu_bh () synchronize_rcu() +#endif extern void synchronize_sched_expedited (void );extern void synchronize_rcu_expedited (void );
@@ -71,13 +75,19 @@ extern void rcu_barrier(void);extern unsigned long rcutorture_testseq;extern unsigned long rcutorture_vernum;extern long rcu_batches_completed (void ); -extern long rcu_batches_completed_bh (void );extern long rcu_batches_completed_sched (void );extern void rcu_force_quiescent_state (void ); -extern void rcu_bh_force_quiescent_state (void );extern void rcu_sched_force_quiescent_state (void ); +#ifndef CONFIG_PREEMPT_RT_FULL +extern void rcu_bh_force_quiescent_state (void ); +extern long rcu_batches_completed_bh (void ); +#else +# define rcu_bh_force_quiescent_state rcu_force_quiescent_state +# define rcu_batches_completed_bh rcu_batches_completed +#endif +/* A context switch is a grace period for RCU-sched and RCU-bh. */ static inline int rcu_blocking_is_gp (void ) {
kernel/rcupdate.c +2 -0
@@ -72,6 +72,7 @@ int debug_lockdep_rcu_enabled(void) }EXPORT_SYMBOL_GPL (debug_lockdep_rcu_enabled); +#ifndef CONFIG_PREEMPT_RT_FULL /** * rcu_read_lock_bh_held() - might we be in RCU-bh read-side critical section? *
@@ -91,6 +92,7 @@ int rcu_read_lock_bh_held(void)return in_softirq () ||irqs_disabled (); }EXPORT_SYMBOL_GPL (rcu_read_lock_bh_held); +#endif #endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
kernel/rcutree.c +10 -0
@@ -166,6 +166,7 @@ void rcu_sched_qs(int cpu) rdp->passed_quiesc =1 ; } +#ifndef CONFIG_PREEMPT_RT_FULL void rcu_bh_qs (int cpu) {struct rcu_data *rdp = &per_cpu (rcu_bh_data, cpu);
@@ -174,6 +175,7 @@ void rcu_bh_qs(int cpu)barrier (); rdp->passed_quiesc = 1; } +#endif /* * Note a context switch. This is a quiescent state for RCU-sched,
@@ -216,6 +218,7 @@ long rcu_batches_completed_sched(void) }EXPORT_SYMBOL_GPL (rcu_batches_completed_sched); +#ifndef CONFIG_PREEMPT_RT_FULL /* * Return the number of RCU BH batches processed thus far for debug & stats. */
@@ -233,6 +236,7 @@ void rcu_bh_force_quiescent_state(void) force_quiescent_state(&rcu_bh_state, 0); }EXPORT_SYMBOL_GPL (rcu_bh_force_quiescent_state); +#endif /* * Record the number of times rcutorture tests have been initiated and
@@ -1579,6 +1583,7 @@ void call_rcu_sched(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) }EXPORT_SYMBOL_GPL (call_rcu_sched); +#ifndef CONFIG_PREEMPT_RT_FULL /* * Queue an RCU for invocation after a quicker grace period. */
@@ -1587,6 +1592,7 @@ void call_rcu_bh(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) __call_rcu(head, func, &rcu_bh_state); }EXPORT_SYMBOL_GPL (call_rcu_bh); +#endif /** * synchronize_sched - wait until an rcu-sched grace period has elapsed.
@@ -1628,6 +1634,7 @@ void synchronize_sched(void) }EXPORT_SYMBOL_GPL (synchronize_sched); +#ifndef CONFIG_PREEMPT_RT_FULL /** * synchronize_rcu_bh - wait until an rcu_bh grace period has elapsed. *
@@ -1653,6 +1660,7 @@ void synchronize_rcu_bh(void) destroy_rcu_head_on_stack(&rcu.head); }EXPORT_SYMBOL_GPL (synchronize_rcu_bh); +#endif /* * Check to see if there is any immediate RCU-related work to be done
@@ -1806,6 +1814,7 @@ static void _rcu_barrier(struct rcu_state *rsp,mutex_unlock (&rcu_barrier_mutex); } +#ifndef CONFIG_PREEMPT_RT_FULL /** * rcu_barrier_bh - Wait until all in-flight call_rcu_bh() callbacks complete. */
@@ -1814,6 +1823,7 @@ void rcu_barrier_bh(void) _rcu_barrier(&rcu_bh_state, call_rcu_bh); }EXPORT_SYMBOL_GPL (rcu_barrier_bh); +#endif /** * rcu_barrier_sched - Wait for in-flight call_rcu_sched() callbacks.