diff options
Diffstat (limited to 'kernel/rcu/update.c')
-rw-r--r-- | kernel/rcu/update.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c index 4056d7992a6c..19b3dacb0753 100644 --- a/kernel/rcu/update.c +++ b/kernel/rcu/update.c | |||
@@ -47,6 +47,7 @@ | |||
47 | #include <linux/hardirq.h> | 47 | #include <linux/hardirq.h> |
48 | #include <linux/delay.h> | 48 | #include <linux/delay.h> |
49 | #include <linux/module.h> | 49 | #include <linux/module.h> |
50 | #include <linux/kthread.h> | ||
50 | 51 | ||
51 | #define CREATE_TRACE_POINTS | 52 | #define CREATE_TRACE_POINTS |
52 | 53 | ||
@@ -347,3 +348,173 @@ static int __init check_cpu_stall_init(void) | |||
347 | early_initcall(check_cpu_stall_init); | 348 | early_initcall(check_cpu_stall_init); |
348 | 349 | ||
349 | #endif /* #ifdef CONFIG_RCU_STALL_COMMON */ | 350 | #endif /* #ifdef CONFIG_RCU_STALL_COMMON */ |
351 | |||
352 | #ifdef CONFIG_TASKS_RCU | ||
353 | |||
354 | /* | ||
355 | * Simple variant of RCU whose quiescent states are voluntary context switch, | ||
356 | * user-space execution, and idle. As such, grace periods can take one good | ||
357 | * long time. There are no read-side primitives similar to rcu_read_lock() | ||
358 | * and rcu_read_unlock() because this implementation is intended to get | ||
359 | * the system into a safe state for some of the manipulations involved in | ||
360 | * tracing and the like. Finally, this implementation does not support | ||
361 | * high call_rcu_tasks() rates from multiple CPUs. If this is required, | ||
362 | * per-CPU callback lists will be needed. | ||
363 | */ | ||
364 | |||
365 | /* Global list of callbacks and associated lock. */ | ||
366 | static struct rcu_head *rcu_tasks_cbs_head; | ||
367 | static struct rcu_head **rcu_tasks_cbs_tail = &rcu_tasks_cbs_head; | ||
368 | static DEFINE_RAW_SPINLOCK(rcu_tasks_cbs_lock); | ||
369 | |||
370 | /* Post an RCU-tasks callback. */ | ||
371 | void call_rcu_tasks(struct rcu_head *rhp, void (*func)(struct rcu_head *rhp)) | ||
372 | { | ||
373 | unsigned long flags; | ||
374 | |||
375 | rhp->next = NULL; | ||
376 | rhp->func = func; | ||
377 | raw_spin_lock_irqsave(&rcu_tasks_cbs_lock, flags); | ||
378 | *rcu_tasks_cbs_tail = rhp; | ||
379 | rcu_tasks_cbs_tail = &rhp->next; | ||
380 | raw_spin_unlock_irqrestore(&rcu_tasks_cbs_lock, flags); | ||
381 | } | ||
382 | EXPORT_SYMBOL_GPL(call_rcu_tasks); | ||
383 | |||
384 | /* See if the current task has stopped holding out, remove from list if so. */ | ||
385 | static void check_holdout_task(struct task_struct *t) | ||
386 | { | ||
387 | if (!ACCESS_ONCE(t->rcu_tasks_holdout) || | ||
388 | t->rcu_tasks_nvcsw != ACCESS_ONCE(t->nvcsw) || | ||
389 | !ACCESS_ONCE(t->on_rq)) { | ||
390 | ACCESS_ONCE(t->rcu_tasks_holdout) = false; | ||
391 | list_del_rcu(&t->rcu_tasks_holdout_list); | ||
392 | put_task_struct(t); | ||
393 | } | ||
394 | } | ||
395 | |||
396 | /* RCU-tasks kthread that detects grace periods and invokes callbacks. */ | ||
397 | static int __noreturn rcu_tasks_kthread(void *arg) | ||
398 | { | ||
399 | unsigned long flags; | ||
400 | struct task_struct *g, *t; | ||
401 | struct rcu_head *list; | ||
402 | struct rcu_head *next; | ||
403 | LIST_HEAD(rcu_tasks_holdouts); | ||
404 | |||
405 | /* FIXME: Add housekeeping affinity. */ | ||
406 | |||
407 | /* | ||
408 | * Each pass through the following loop makes one check for | ||
409 | * newly arrived callbacks, and, if there are some, waits for | ||
410 | * one RCU-tasks grace period and then invokes the callbacks. | ||
411 | * This loop is terminated by the system going down. ;-) | ||
412 | */ | ||
413 | for (;;) { | ||
414 | |||
415 | /* Pick up any new callbacks. */ | ||
416 | raw_spin_lock_irqsave(&rcu_tasks_cbs_lock, flags); | ||
417 | list = rcu_tasks_cbs_head; | ||
418 | rcu_tasks_cbs_head = NULL; | ||
419 | rcu_tasks_cbs_tail = &rcu_tasks_cbs_head; | ||
420 | raw_spin_unlock_irqrestore(&rcu_tasks_cbs_lock, flags); | ||
421 | |||
422 | /* If there were none, wait a bit and start over. */ | ||
423 | if (!list) { | ||
424 | schedule_timeout_interruptible(HZ); | ||
425 | WARN_ON(signal_pending(current)); | ||
426 | continue; | ||
427 | } | ||
428 | |||
429 | /* | ||
430 | * Wait for all pre-existing t->on_rq and t->nvcsw | ||
431 | * transitions to complete. Invoking synchronize_sched() | ||
432 | * suffices because all these transitions occur with | ||
433 | * interrupts disabled. Without this synchronize_sched(), | ||
434 | * a read-side critical section that started before the | ||
435 | * grace period might be incorrectly seen as having started | ||
436 | * after the grace period. | ||
437 | * | ||
438 | * This synchronize_sched() also dispenses with the | ||
439 | * need for a memory barrier on the first store to | ||
440 | * ->rcu_tasks_holdout, as it forces the store to happen | ||
441 | * after the beginning of the grace period. | ||
442 | */ | ||
443 | synchronize_sched(); | ||
444 | |||
445 | /* | ||
446 | * There were callbacks, so we need to wait for an | ||
447 | * RCU-tasks grace period. Start off by scanning | ||
448 | * the task list for tasks that are not already | ||
449 | * voluntarily blocked. Mark these tasks and make | ||
450 | * a list of them in rcu_tasks_holdouts. | ||
451 | */ | ||
452 | rcu_read_lock(); | ||
453 | for_each_process_thread(g, t) { | ||
454 | if (t != current && ACCESS_ONCE(t->on_rq) && | ||
455 | !is_idle_task(t)) { | ||
456 | get_task_struct(t); | ||
457 | t->rcu_tasks_nvcsw = ACCESS_ONCE(t->nvcsw); | ||
458 | ACCESS_ONCE(t->rcu_tasks_holdout) = true; | ||
459 | list_add(&t->rcu_tasks_holdout_list, | ||
460 | &rcu_tasks_holdouts); | ||
461 | } | ||
462 | } | ||
463 | rcu_read_unlock(); | ||
464 | |||
465 | /* | ||
466 | * Each pass through the following loop scans the list | ||
467 | * of holdout tasks, removing any that are no longer | ||
468 | * holdouts. When the list is empty, we are done. | ||
469 | */ | ||
470 | while (!list_empty(&rcu_tasks_holdouts)) { | ||
471 | schedule_timeout_interruptible(HZ); | ||
472 | WARN_ON(signal_pending(current)); | ||
473 | rcu_read_lock(); | ||
474 | list_for_each_entry_rcu(t, &rcu_tasks_holdouts, | ||
475 | rcu_tasks_holdout_list) | ||
476 | check_holdout_task(t); | ||
477 | rcu_read_unlock(); | ||
478 | } | ||
479 | |||
480 | /* | ||
481 | * Because ->on_rq and ->nvcsw are not guaranteed | ||
482 | * to have a full memory barriers prior to them in the | ||
483 | * schedule() path, memory reordering on other CPUs could | ||
484 | * cause their RCU-tasks read-side critical sections to | ||
485 | * extend past the end of the grace period. However, | ||
486 | * because these ->nvcsw updates are carried out with | ||
487 | * interrupts disabled, we can use synchronize_sched() | ||
488 | * to force the needed ordering on all such CPUs. | ||
489 | * | ||
490 | * This synchronize_sched() also confines all | ||
491 | * ->rcu_tasks_holdout accesses to be within the grace | ||
492 | * period, avoiding the need for memory barriers for | ||
493 | * ->rcu_tasks_holdout accesses. | ||
494 | */ | ||
495 | synchronize_sched(); | ||
496 | |||
497 | /* Invoke the callbacks. */ | ||
498 | while (list) { | ||
499 | next = list->next; | ||
500 | local_bh_disable(); | ||
501 | list->func(list); | ||
502 | local_bh_enable(); | ||
503 | list = next; | ||
504 | cond_resched(); | ||
505 | } | ||
506 | } | ||
507 | } | ||
508 | |||
509 | /* Spawn rcu_tasks_kthread() at boot time. */ | ||
510 | static int __init rcu_spawn_tasks_kthread(void) | ||
511 | { | ||
512 | struct task_struct __maybe_unused *t; | ||
513 | |||
514 | t = kthread_run(rcu_tasks_kthread, NULL, "rcu_tasks_kthread"); | ||
515 | BUG_ON(IS_ERR(t)); | ||
516 | return 0; | ||
517 | } | ||
518 | early_initcall(rcu_spawn_tasks_kthread); | ||
519 | |||
520 | #endif /* #ifdef CONFIG_TASKS_RCU */ | ||