diff options
author | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2009-10-28 11:14:49 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-11-02 10:06:21 -0500 |
commit | 83f5b01ffbbaea6f97c9a79d21e240dbfb69f2f1 (patch) | |
tree | e278471f3b9851028ef60e283ce8c73772531d82 /kernel/rcutree.c | |
parent | b00bc0b237055b4c45816325ee14f0bd83e6f590 (diff) |
rcu: Fix long-grace-period race between forcing and initialization
Very long RCU read-side critical sections (50 milliseconds or
so) can cause a race between force_quiescent_state() and
rcu_start_gp() as follows on kernel builds with multi-level
rcu_node hierarchies:
1. CPU 0 calls force_quiescent_state(), sees that there is a
grace period in progress, and acquires ->fsqlock.
2. CPU 1 detects the end of the grace period, and so
cpu_quiet_msk_finish() sets rsp->completed to rsp->gpnum.
This operation is carried out under the root rnp->lock,
but CPU 0 has not yet acquired that lock. Note that
rsp->signaled is still RCU_SAVE_DYNTICK from the last
grace period.
3. CPU 1 calls rcu_start_gp(), but no one wants a new grace
period, so it drops the root rnp->lock and returns.
4. CPU 0 acquires the root rnp->lock and picks up rsp->completed
and rsp->signaled, then drops rnp->lock. It then enters the
RCU_SAVE_DYNTICK leg of the switch statement.
5. CPU 2 invokes call_rcu(), and now needs a new grace period.
It calls rcu_start_gp(), which acquires the root rnp->lock, sets
rsp->signaled to RCU_GP_INIT (too bad that CPU 0 is already in
the RCU_SAVE_DYNTICK leg of the switch statement!) and starts
initializing the rcu_node hierarchy. If there are multiple
levels to the hierarchy, it will drop the root rnp->lock and
initialize the lower levels of the hierarchy.
6. CPU 0 notes that rsp->completed has not changed, which permits
both CPU 2 and CPU 0 to try updating it concurrently. If CPU 0's
update prevails, later calls to force_quiescent_state() can
count old quiescent states against the new grace period, which
can in turn result in premature ending of grace periods.
Not good.
This patch adds an RCU_GP_IDLE state for rsp->signaled that is
set initially at boot time and any time a grace period ends.
This prevents CPU 0 from getting into the workings of
force_quiescent_state() in step 4. Additional locking and
checks prevent the concurrent update of rsp->signaled in step 6.
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: laijs@cn.fujitsu.com
Cc: dipankar@in.ibm.com
Cc: mathieu.desnoyers@polymtl.ca
Cc: josh@joshtriplett.org
Cc: dvhltc@us.ibm.com
Cc: niv@us.ibm.com
Cc: peterz@infradead.org
Cc: rostedt@goodmis.org
Cc: Valdis.Kletnieks@vt.edu
Cc: dhowells@redhat.com
LKML-Reference: <1256742889199-git-send-email->
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'kernel/rcutree.c')
-rw-r--r-- | kernel/rcutree.c | 16 |
1 files changed, 11 insertions, 5 deletions
diff --git a/kernel/rcutree.c b/kernel/rcutree.c index 0536125b0497..f3077c0ab181 100644 --- a/kernel/rcutree.c +++ b/kernel/rcutree.c | |||
@@ -59,7 +59,7 @@ | |||
59 | NUM_RCU_LVL_2, \ | 59 | NUM_RCU_LVL_2, \ |
60 | NUM_RCU_LVL_3, /* == MAX_RCU_LVLS */ \ | 60 | NUM_RCU_LVL_3, /* == MAX_RCU_LVLS */ \ |
61 | }, \ | 61 | }, \ |
62 | .signaled = RCU_SIGNAL_INIT, \ | 62 | .signaled = RCU_GP_IDLE, \ |
63 | .gpnum = -300, \ | 63 | .gpnum = -300, \ |
64 | .completed = -300, \ | 64 | .completed = -300, \ |
65 | .onofflock = __SPIN_LOCK_UNLOCKED(&name.onofflock), \ | 65 | .onofflock = __SPIN_LOCK_UNLOCKED(&name.onofflock), \ |
@@ -657,14 +657,17 @@ rcu_start_gp(struct rcu_state *rsp, unsigned long flags) | |||
657 | * irqs disabled. | 657 | * irqs disabled. |
658 | */ | 658 | */ |
659 | rcu_for_each_node_breadth_first(rsp, rnp) { | 659 | rcu_for_each_node_breadth_first(rsp, rnp) { |
660 | spin_lock(&rnp->lock); /* irqs already disabled. */ | 660 | spin_lock(&rnp->lock); /* irqs already disabled. */ |
661 | rcu_preempt_check_blocked_tasks(rnp); | 661 | rcu_preempt_check_blocked_tasks(rnp); |
662 | rnp->qsmask = rnp->qsmaskinit; | 662 | rnp->qsmask = rnp->qsmaskinit; |
663 | rnp->gpnum = rsp->gpnum; | 663 | rnp->gpnum = rsp->gpnum; |
664 | spin_unlock(&rnp->lock); /* irqs already disabled. */ | 664 | spin_unlock(&rnp->lock); /* irqs remain disabled. */ |
665 | } | 665 | } |
666 | 666 | ||
667 | rnp = rcu_get_root(rsp); | ||
668 | spin_lock(&rnp->lock); /* irqs already disabled. */ | ||
667 | rsp->signaled = RCU_SIGNAL_INIT; /* force_quiescent_state now OK. */ | 669 | rsp->signaled = RCU_SIGNAL_INIT; /* force_quiescent_state now OK. */ |
670 | spin_unlock(&rnp->lock); /* irqs remain disabled. */ | ||
668 | spin_unlock_irqrestore(&rsp->onofflock, flags); | 671 | spin_unlock_irqrestore(&rsp->onofflock, flags); |
669 | } | 672 | } |
670 | 673 | ||
@@ -706,6 +709,7 @@ static void cpu_quiet_msk_finish(struct rcu_state *rsp, unsigned long flags) | |||
706 | { | 709 | { |
707 | WARN_ON_ONCE(!rcu_gp_in_progress(rsp)); | 710 | WARN_ON_ONCE(!rcu_gp_in_progress(rsp)); |
708 | rsp->completed = rsp->gpnum; | 711 | rsp->completed = rsp->gpnum; |
712 | rsp->signaled = RCU_GP_IDLE; | ||
709 | rcu_process_gp_end(rsp, rsp->rda[smp_processor_id()]); | 713 | rcu_process_gp_end(rsp, rsp->rda[smp_processor_id()]); |
710 | rcu_start_gp(rsp, flags); /* releases root node's rnp->lock. */ | 714 | rcu_start_gp(rsp, flags); /* releases root node's rnp->lock. */ |
711 | } | 715 | } |
@@ -1162,9 +1166,10 @@ static void force_quiescent_state(struct rcu_state *rsp, int relaxed) | |||
1162 | } | 1166 | } |
1163 | spin_unlock(&rnp->lock); | 1167 | spin_unlock(&rnp->lock); |
1164 | switch (signaled) { | 1168 | switch (signaled) { |
1169 | case RCU_GP_IDLE: | ||
1165 | case RCU_GP_INIT: | 1170 | case RCU_GP_INIT: |
1166 | 1171 | ||
1167 | break; /* grace period still initializing, ignore. */ | 1172 | break; /* grace period idle or initializing, ignore. */ |
1168 | 1173 | ||
1169 | case RCU_SAVE_DYNTICK: | 1174 | case RCU_SAVE_DYNTICK: |
1170 | 1175 | ||
@@ -1178,7 +1183,8 @@ static void force_quiescent_state(struct rcu_state *rsp, int relaxed) | |||
1178 | 1183 | ||
1179 | /* Update state, record completion counter. */ | 1184 | /* Update state, record completion counter. */ |
1180 | spin_lock(&rnp->lock); | 1185 | spin_lock(&rnp->lock); |
1181 | if (lastcomp == rsp->completed) { | 1186 | if (lastcomp == rsp->completed && |
1187 | rsp->signaled == RCU_SAVE_DYNTICK) { | ||
1182 | rsp->signaled = RCU_FORCE_QS; | 1188 | rsp->signaled = RCU_FORCE_QS; |
1183 | dyntick_record_completed(rsp, lastcomp); | 1189 | dyntick_record_completed(rsp, lastcomp); |
1184 | } | 1190 | } |