diff options
author | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2017-03-24 16:46:33 -0400 |
---|---|---|
committer | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2017-04-18 14:38:22 -0400 |
commit | f60d231a87c5c9f23f10e69996f396d46f5bf901 (patch) | |
tree | 04076adf019c43dbf7f31ad435aed8361a9a46b0 | |
parent | 80a7956fe36c2ee40c6ff12c77926d267802b7c8 (diff) |
srcu: Crude control of expedited grace periods
SRCU's implementation of expedited grace periods has always assumed
that the SRCU instance is idle when the expedited request arrives.
This commit improves this a bit by maintaining a count of the number
of outstanding expedited requests, thus allowing prior non-expedited
grace periods accommodate these requests by shifting to expedited mode.
However, any non-expedited wait already in progress will still wait for
the full duration.
Improved control of expedited grace periods is planned, but one step
at a time.
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
-rw-r--r-- | include/linux/srcu.h | 1 | ||||
-rw-r--r-- | kernel/rcu/srcu.c | 84 |
2 files changed, 47 insertions, 38 deletions
diff --git a/include/linux/srcu.h b/include/linux/srcu.h index e7dbc01b61a1..73a1b6296224 100644 --- a/include/linux/srcu.h +++ b/include/linux/srcu.h | |||
@@ -42,6 +42,7 @@ struct srcu_array { | |||
42 | struct srcu_struct { | 42 | struct srcu_struct { |
43 | unsigned long completed; | 43 | unsigned long completed; |
44 | unsigned long srcu_gp_seq; | 44 | unsigned long srcu_gp_seq; |
45 | atomic_t srcu_exp_cnt; | ||
45 | struct srcu_array __percpu *per_cpu_ref; | 46 | struct srcu_array __percpu *per_cpu_ref; |
46 | spinlock_t queue_lock; /* protect ->srcu_cblist */ | 47 | spinlock_t queue_lock; /* protect ->srcu_cblist */ |
47 | struct rcu_segcblist srcu_cblist; | 48 | struct rcu_segcblist srcu_cblist; |
diff --git a/kernel/rcu/srcu.c b/kernel/rcu/srcu.c index 90ffea31b188..3cfcc59bddf3 100644 --- a/kernel/rcu/srcu.c +++ b/kernel/rcu/srcu.c | |||
@@ -43,6 +43,7 @@ static int init_srcu_struct_fields(struct srcu_struct *sp) | |||
43 | { | 43 | { |
44 | sp->completed = 0; | 44 | sp->completed = 0; |
45 | sp->srcu_gp_seq = 0; | 45 | sp->srcu_gp_seq = 0; |
46 | atomic_set(&sp->srcu_exp_cnt, 0); | ||
46 | spin_lock_init(&sp->queue_lock); | 47 | spin_lock_init(&sp->queue_lock); |
47 | rcu_segcblist_init(&sp->srcu_cblist); | 48 | rcu_segcblist_init(&sp->srcu_cblist); |
48 | INIT_DELAYED_WORK(&sp->work, process_srcu); | 49 | INIT_DELAYED_WORK(&sp->work, process_srcu); |
@@ -179,7 +180,6 @@ static bool srcu_readers_active(struct srcu_struct *sp) | |||
179 | return sum; | 180 | return sum; |
180 | } | 181 | } |
181 | 182 | ||
182 | #define SRCU_CALLBACK_BATCH 10 | ||
183 | #define SRCU_INTERVAL 1 | 183 | #define SRCU_INTERVAL 1 |
184 | 184 | ||
185 | /** | 185 | /** |
@@ -197,6 +197,7 @@ static bool srcu_readers_active(struct srcu_struct *sp) | |||
197 | */ | 197 | */ |
198 | void cleanup_srcu_struct(struct srcu_struct *sp) | 198 | void cleanup_srcu_struct(struct srcu_struct *sp) |
199 | { | 199 | { |
200 | WARN_ON_ONCE(atomic_read(&sp->srcu_exp_cnt)); | ||
200 | if (WARN_ON(srcu_readers_active(sp))) | 201 | if (WARN_ON(srcu_readers_active(sp))) |
201 | return; /* Leakage unless caller handles error. */ | 202 | return; /* Leakage unless caller handles error. */ |
202 | if (WARN_ON(!rcu_segcblist_empty(&sp->srcu_cblist))) | 203 | if (WARN_ON(!rcu_segcblist_empty(&sp->srcu_cblist))) |
@@ -244,13 +245,10 @@ EXPORT_SYMBOL_GPL(__srcu_read_unlock); | |||
244 | * We use an adaptive strategy for synchronize_srcu() and especially for | 245 | * We use an adaptive strategy for synchronize_srcu() and especially for |
245 | * synchronize_srcu_expedited(). We spin for a fixed time period | 246 | * synchronize_srcu_expedited(). We spin for a fixed time period |
246 | * (defined below) to allow SRCU readers to exit their read-side critical | 247 | * (defined below) to allow SRCU readers to exit their read-side critical |
247 | * sections. If there are still some readers after 10 microseconds, | 248 | * sections. If there are still some readers after a few microseconds, |
248 | * we repeatedly block for 1-millisecond time periods. This approach | 249 | * we repeatedly block for 1-millisecond time periods. |
249 | * has done well in testing, so there is no need for a config parameter. | ||
250 | */ | 250 | */ |
251 | #define SRCU_RETRY_CHECK_DELAY 5 | 251 | #define SRCU_RETRY_CHECK_DELAY 5 |
252 | #define SYNCHRONIZE_SRCU_TRYCOUNT 2 | ||
253 | #define SYNCHRONIZE_SRCU_EXP_TRYCOUNT 12 | ||
254 | 252 | ||
255 | /* | 253 | /* |
256 | * Start an SRCU grace period. | 254 | * Start an SRCU grace period. |
@@ -267,16 +265,16 @@ static void srcu_gp_start(struct srcu_struct *sp) | |||
267 | } | 265 | } |
268 | 266 | ||
269 | /* | 267 | /* |
270 | * Wait until all readers counted by array index idx complete, but loop | 268 | * Wait until all readers counted by array index idx complete, but |
271 | * a maximum of trycount times. The caller must ensure that ->completed | 269 | * loop an additional time if there is an expedited grace period pending. |
272 | * is not changed while checking. | 270 | * The caller must ensure that ->completed is not changed while checking. |
273 | */ | 271 | */ |
274 | static bool try_check_zero(struct srcu_struct *sp, int idx, int trycount) | 272 | static bool try_check_zero(struct srcu_struct *sp, int idx, int trycount) |
275 | { | 273 | { |
276 | for (;;) { | 274 | for (;;) { |
277 | if (srcu_readers_active_idx_check(sp, idx)) | 275 | if (srcu_readers_active_idx_check(sp, idx)) |
278 | return true; | 276 | return true; |
279 | if (--trycount <= 0) | 277 | if (--trycount + !!atomic_read(&sp->srcu_exp_cnt) <= 0) |
280 | return false; | 278 | return false; |
281 | udelay(SRCU_RETRY_CHECK_DELAY); | 279 | udelay(SRCU_RETRY_CHECK_DELAY); |
282 | } | 280 | } |
@@ -364,7 +362,7 @@ static void srcu_reschedule(struct srcu_struct *sp, unsigned long delay); | |||
364 | /* | 362 | /* |
365 | * Helper function for synchronize_srcu() and synchronize_srcu_expedited(). | 363 | * Helper function for synchronize_srcu() and synchronize_srcu_expedited(). |
366 | */ | 364 | */ |
367 | static void __synchronize_srcu(struct srcu_struct *sp, int trycount) | 365 | static void __synchronize_srcu(struct srcu_struct *sp) |
368 | { | 366 | { |
369 | struct rcu_synchronize rcu; | 367 | struct rcu_synchronize rcu; |
370 | struct rcu_head *head = &rcu.head; | 368 | struct rcu_head *head = &rcu.head; |
@@ -401,6 +399,32 @@ static void __synchronize_srcu(struct srcu_struct *sp, int trycount) | |||
401 | } | 399 | } |
402 | 400 | ||
403 | /** | 401 | /** |
402 | * synchronize_srcu_expedited - Brute-force SRCU grace period | ||
403 | * @sp: srcu_struct with which to synchronize. | ||
404 | * | ||
405 | * Wait for an SRCU grace period to elapse, but be more aggressive about | ||
406 | * spinning rather than blocking when waiting. | ||
407 | * | ||
408 | * Note that synchronize_srcu_expedited() has the same deadlock and | ||
409 | * memory-ordering properties as does synchronize_srcu(). | ||
410 | */ | ||
411 | void synchronize_srcu_expedited(struct srcu_struct *sp) | ||
412 | { | ||
413 | bool do_norm = rcu_gp_is_normal(); | ||
414 | |||
415 | if (!do_norm) { | ||
416 | atomic_inc(&sp->srcu_exp_cnt); | ||
417 | smp_mb__after_atomic(); /* increment before GP. */ | ||
418 | } | ||
419 | __synchronize_srcu(sp); | ||
420 | if (!do_norm) { | ||
421 | smp_mb__before_atomic(); /* GP before decrement. */ | ||
422 | atomic_dec(&sp->srcu_exp_cnt); | ||
423 | } | ||
424 | } | ||
425 | EXPORT_SYMBOL_GPL(synchronize_srcu_expedited); | ||
426 | |||
427 | /** | ||
404 | * synchronize_srcu - wait for prior SRCU read-side critical-section completion | 428 | * synchronize_srcu - wait for prior SRCU read-side critical-section completion |
405 | * @sp: srcu_struct with which to synchronize. | 429 | * @sp: srcu_struct with which to synchronize. |
406 | * | 430 | * |
@@ -441,29 +465,14 @@ static void __synchronize_srcu(struct srcu_struct *sp, int trycount) | |||
441 | */ | 465 | */ |
442 | void synchronize_srcu(struct srcu_struct *sp) | 466 | void synchronize_srcu(struct srcu_struct *sp) |
443 | { | 467 | { |
444 | __synchronize_srcu(sp, (rcu_gp_is_expedited() && !rcu_gp_is_normal()) | 468 | if (rcu_gp_is_expedited()) |
445 | ? SYNCHRONIZE_SRCU_EXP_TRYCOUNT | 469 | synchronize_srcu_expedited(sp); |
446 | : SYNCHRONIZE_SRCU_TRYCOUNT); | 470 | else |
471 | __synchronize_srcu(sp); | ||
447 | } | 472 | } |
448 | EXPORT_SYMBOL_GPL(synchronize_srcu); | 473 | EXPORT_SYMBOL_GPL(synchronize_srcu); |
449 | 474 | ||
450 | /** | 475 | /** |
451 | * synchronize_srcu_expedited - Brute-force SRCU grace period | ||
452 | * @sp: srcu_struct with which to synchronize. | ||
453 | * | ||
454 | * Wait for an SRCU grace period to elapse, but be more aggressive about | ||
455 | * spinning rather than blocking when waiting. | ||
456 | * | ||
457 | * Note that synchronize_srcu_expedited() has the same deadlock and | ||
458 | * memory-ordering properties as does synchronize_srcu(). | ||
459 | */ | ||
460 | void synchronize_srcu_expedited(struct srcu_struct *sp) | ||
461 | { | ||
462 | __synchronize_srcu(sp, SYNCHRONIZE_SRCU_EXP_TRYCOUNT); | ||
463 | } | ||
464 | EXPORT_SYMBOL_GPL(synchronize_srcu_expedited); | ||
465 | |||
466 | /** | ||
467 | * srcu_barrier - Wait until all in-flight call_srcu() callbacks complete. | 476 | * srcu_barrier - Wait until all in-flight call_srcu() callbacks complete. |
468 | * @sp: srcu_struct on which to wait for in-flight callbacks. | 477 | * @sp: srcu_struct on which to wait for in-flight callbacks. |
469 | */ | 478 | */ |
@@ -490,7 +499,7 @@ EXPORT_SYMBOL_GPL(srcu_batches_completed); | |||
490 | * Core SRCU state machine. Advance callbacks from ->batch_check0 to | 499 | * Core SRCU state machine. Advance callbacks from ->batch_check0 to |
491 | * ->batch_check1 and then to ->batch_done as readers drain. | 500 | * ->batch_check1 and then to ->batch_done as readers drain. |
492 | */ | 501 | */ |
493 | static void srcu_advance_batches(struct srcu_struct *sp, int trycount) | 502 | static void srcu_advance_batches(struct srcu_struct *sp) |
494 | { | 503 | { |
495 | int idx; | 504 | int idx; |
496 | 505 | ||
@@ -521,8 +530,8 @@ static void srcu_advance_batches(struct srcu_struct *sp, int trycount) | |||
521 | 530 | ||
522 | if (rcu_seq_state(READ_ONCE(sp->srcu_gp_seq)) == SRCU_STATE_SCAN1) { | 531 | if (rcu_seq_state(READ_ONCE(sp->srcu_gp_seq)) == SRCU_STATE_SCAN1) { |
523 | idx = 1 ^ (sp->completed & 1); | 532 | idx = 1 ^ (sp->completed & 1); |
524 | if (!try_check_zero(sp, idx, trycount)) | 533 | if (!try_check_zero(sp, idx, 1)) |
525 | return; /* readers present, retry after SRCU_INTERVAL */ | 534 | return; /* readers present, retry later. */ |
526 | srcu_flip(sp); | 535 | srcu_flip(sp); |
527 | rcu_seq_set_state(&sp->srcu_gp_seq, SRCU_STATE_SCAN2); | 536 | rcu_seq_set_state(&sp->srcu_gp_seq, SRCU_STATE_SCAN2); |
528 | } | 537 | } |
@@ -534,9 +543,8 @@ static void srcu_advance_batches(struct srcu_struct *sp, int trycount) | |||
534 | * so check at least twice in quick succession after a flip. | 543 | * so check at least twice in quick succession after a flip. |
535 | */ | 544 | */ |
536 | idx = 1 ^ (sp->completed & 1); | 545 | idx = 1 ^ (sp->completed & 1); |
537 | trycount = trycount < 2 ? 2 : trycount; | 546 | if (!try_check_zero(sp, idx, 2)) |
538 | if (!try_check_zero(sp, idx, trycount)) | 547 | return; /* readers present, retry after later. */ |
539 | return; /* readers present, retry after SRCU_INTERVAL */ | ||
540 | srcu_gp_end(sp); | 548 | srcu_gp_end(sp); |
541 | } | 549 | } |
542 | } | 550 | } |
@@ -602,8 +610,8 @@ void process_srcu(struct work_struct *work) | |||
602 | 610 | ||
603 | sp = container_of(work, struct srcu_struct, work.work); | 611 | sp = container_of(work, struct srcu_struct, work.work); |
604 | 612 | ||
605 | srcu_advance_batches(sp, 1); | 613 | srcu_advance_batches(sp); |
606 | srcu_invoke_callbacks(sp); | 614 | srcu_invoke_callbacks(sp); |
607 | srcu_reschedule(sp, SRCU_INTERVAL); | 615 | srcu_reschedule(sp, atomic_read(&sp->srcu_exp_cnt) ? 0 : SRCU_INTERVAL); |
608 | } | 616 | } |
609 | EXPORT_SYMBOL_GPL(process_srcu); | 617 | EXPORT_SYMBOL_GPL(process_srcu); |