mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
srcu: Make Tiny call_srcu() safe to call from any context
Give Tiny call_srcu() the same treatment as Tree SRCU. When interrupts are disabled and the scheduler is up, stage the callback on the srcu_struct's lockless list for an irq_work to re-issue later. Tiny SRCU is uniprocessor, so there is no CPU-offline drain. A draining flag drops a deferring call_srcu() that re-enters mid-drain (unless from an NMI), as in Tree SRCU; such a drop can strand state the caller tied to the callback, not just the callback itself. Interrupts stay off for the whole batch. TINY_SRCU implies !SMP, where arch_irq_work_has_interrupt() is false, so the drain always waits for the tick and a batch is whatever one tick's worth of interrupts-disabled call_srcu()s staged. Unlike the other three flavors srcu_do_enqueue() here has no debug_rcu_head_queue(), so nothing reports a double call_srcu(); termination of the drain rests on srcu_do_enqueue() clearing ->next, and the callback list self-links at the tail exactly as a double call_srcu() made it before. srcu_barrier() (now out of line) and cleanup_srcu_struct() drain the deferred list first, so a deferred callback is re-issued onto the callback list and invoked by the grace-period work that cleanup_srcu_struct() flushes, rather than stranded on a soon-to-be-freed srcu_struct. cleanup_srcu_struct() also syncs ->defer_iw, since that irq_work is embedded in the srcu_struct the caller is about to free. The draining flag is global rather than per-srcu_struct: a re-entrant call_srcu(B) inside a drain of A raises B's own ->defer_iw, whose drain can stage back onto A, so a per-srcu_struct flag would not break the chain. The cost is that a drain of A also drops a non-NMI call_srcu() to any other srcu_struct for its duration. Gated by CONFIG_RCU_DEFER like Tree SRCU, though the srcu_struct members are unconditional. Suggested-by: Paul E. McKenney <paulmck@kernel.org> Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
This commit is contained in:
parent
35a26d8704
commit
a04b2854be
|
|
@ -12,6 +12,7 @@
|
|||
#define _LINUX_SRCU_TINY_H
|
||||
|
||||
#include <linux/irq_work_types.h>
|
||||
#include <linux/llist.h>
|
||||
#include <linux/swait.h>
|
||||
|
||||
struct srcu_struct {
|
||||
|
|
@ -26,6 +27,8 @@ struct srcu_struct {
|
|||
struct rcu_head **srcu_cb_tail; /* Pending callbacks: Tail. */
|
||||
struct work_struct srcu_work; /* For driving grace periods. */
|
||||
struct irq_work srcu_irq_work; /* Defer schedule_work() to irq work. */
|
||||
struct llist_head defer_cbs; /* Callbacks deferred on re-entry. */
|
||||
struct irq_work defer_iw; /* Re-issues defer_cbs later. */
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
struct lockdep_map dep_map;
|
||||
#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
|
||||
|
|
@ -33,6 +36,7 @@ struct srcu_struct {
|
|||
|
||||
void srcu_drive_gp(struct work_struct *wp);
|
||||
void srcu_tiny_irq_work(struct irq_work *irq_work);
|
||||
void srcu_defer_drain(struct irq_work *irq_work);
|
||||
|
||||
#define __SRCU_STRUCT_INIT(name, __ignored, ___ignored, ____ignored) \
|
||||
{ \
|
||||
|
|
@ -40,6 +44,9 @@ void srcu_tiny_irq_work(struct irq_work *irq_work);
|
|||
.srcu_cb_tail = &name.srcu_cb_head, \
|
||||
.srcu_work = __WORK_INITIALIZER(name.srcu_work, srcu_drive_gp), \
|
||||
.srcu_irq_work = { .func = srcu_tiny_irq_work }, \
|
||||
.defer_cbs = LLIST_HEAD_INIT(name.defer_cbs), \
|
||||
.defer_iw = { .node = { .u_flags = IRQ_WORK_HARD_IRQ }, \
|
||||
.func = srcu_defer_drain }, \
|
||||
__SRCU_DEP_MAP_INIT(name) \
|
||||
}
|
||||
|
||||
|
|
@ -131,10 +138,7 @@ static inline void synchronize_srcu_expedited(struct srcu_struct *ssp)
|
|||
synchronize_srcu(ssp);
|
||||
}
|
||||
|
||||
static inline void srcu_barrier(struct srcu_struct *ssp)
|
||||
{
|
||||
synchronize_srcu(ssp);
|
||||
}
|
||||
void srcu_barrier(struct srcu_struct *ssp);
|
||||
|
||||
static inline void srcu_expedite_current(struct srcu_struct *ssp) { }
|
||||
#define srcu_check_read_flavor(ssp, read_flavor) do { } while (0)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <linux/export.h>
|
||||
#include <linux/irq_work.h>
|
||||
#include <linux/llist.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/preempt.h>
|
||||
#include <linux/rcupdate_wait.h>
|
||||
|
|
@ -29,6 +30,8 @@ extern int rcu_scheduler_active;
|
|||
static LIST_HEAD(srcu_boot_list);
|
||||
static bool srcu_init_done;
|
||||
|
||||
static void __srcu_defer_drain(struct srcu_struct *ssp);
|
||||
|
||||
static int init_srcu_struct_fields(struct srcu_struct *ssp)
|
||||
{
|
||||
ssp->srcu_lock_nesting[0] = 0;
|
||||
|
|
@ -43,6 +46,8 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp)
|
|||
INIT_WORK(&ssp->srcu_work, srcu_drive_gp);
|
||||
INIT_LIST_HEAD(&ssp->srcu_work.entry);
|
||||
init_irq_work(&ssp->srcu_irq_work, srcu_tiny_irq_work);
|
||||
init_llist_head(&ssp->defer_cbs);
|
||||
ssp->defer_iw = IRQ_WORK_INIT_HARD(srcu_defer_drain);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +91,16 @@ EXPORT_SYMBOL_GPL(init_srcu_struct_generic);
|
|||
void cleanup_srcu_struct(struct srcu_struct *ssp)
|
||||
{
|
||||
WARN_ON(srcu_readers_active(ssp));
|
||||
/*
|
||||
* Re-issue any deferred callbacks, then wait out ->defer_iw before it is
|
||||
* freed. Skipped entirely with CONFIG_RCU_DEFER=n: irq_work_sync() ends
|
||||
* in an unconditional synchronize_rcu() wherever
|
||||
* arch_irq_work_has_interrupt() is false, which is every !SMP target.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_RCU_DEFER)) {
|
||||
__srcu_defer_drain(ssp);
|
||||
irq_work_sync(&ssp->defer_iw);
|
||||
}
|
||||
irq_work_sync(&ssp->srcu_irq_work);
|
||||
flush_work(&ssp->srcu_work);
|
||||
WARN_ON(ssp->srcu_gp_running);
|
||||
|
|
@ -213,11 +228,11 @@ static void srcu_gp_start_if_needed(struct srcu_struct *ssp)
|
|||
}
|
||||
|
||||
/*
|
||||
* Enqueue an SRCU callback on the specified srcu_struct structure,
|
||||
* initiating grace-period processing if it is not already running.
|
||||
* Also called by __srcu_defer_drain() to re-issue a deferred callback, so it
|
||||
* must not re-check the deferral condition.
|
||||
*/
|
||||
void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
|
||||
rcu_callback_t func)
|
||||
static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,
|
||||
rcu_callback_t func)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
|
|
@ -231,6 +246,68 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
|
|||
srcu_gp_start_if_needed(ssp);
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
/*
|
||||
* Set only by the irq_work drain, the one drain its own re-issue can re-feed;
|
||||
* a callback staged during a direct drain is taken by ->defer_iw afterwards.
|
||||
* Global rather than per-srcu_struct: a re-entrant call_srcu(B) inside a drain
|
||||
* of A raises B's own ->defer_iw, whose drain can stage back onto A.
|
||||
*/
|
||||
static bool srcu_defer_draining;
|
||||
|
||||
static void __srcu_defer_drain(struct srcu_struct *ssp)
|
||||
{
|
||||
struct llist_node *node, *next;
|
||||
unsigned long flags;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_RCU_DEFER))
|
||||
return;
|
||||
|
||||
/* Re-issued newest-first; nothing depends on call_srcu() ordering. */
|
||||
local_irq_save(flags);
|
||||
llist_for_each_safe(node, next, llist_del_all(&ssp->defer_cbs)) {
|
||||
struct rcu_head *rhp = (struct rcu_head *)node;
|
||||
|
||||
srcu_do_enqueue(ssp, rhp, rhp->func);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
/* Only the irq_work drain can be re-fed by its own re-issue; see Tree SRCU. */
|
||||
void srcu_defer_drain(struct irq_work *iw)
|
||||
{
|
||||
struct srcu_struct *ssp = container_of(iw, struct srcu_struct, defer_iw);
|
||||
|
||||
WRITE_ONCE(srcu_defer_draining, true);
|
||||
__srcu_defer_drain(ssp);
|
||||
WRITE_ONCE(srcu_defer_draining, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(srcu_defer_drain);
|
||||
|
||||
void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
|
||||
rcu_callback_t func)
|
||||
{
|
||||
if (should_rcu_defer()) {
|
||||
/* A re-entrant call_srcu() during the drain would livelock it. */
|
||||
if (READ_ONCE(srcu_defer_draining) && !in_nmi()) {
|
||||
WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU),
|
||||
"call_srcu() re-entered during callback drain; leaking callback\n");
|
||||
return;
|
||||
}
|
||||
rhp->func = func;
|
||||
if (llist_add((struct llist_node *)rhp, &ssp->defer_cbs))
|
||||
irq_work_queue(&ssp->defer_iw);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only reachable from an NMI when deferral is off: before the scheduler
|
||||
* is up, or with CONFIG_RCU_DEFER=n. The enqueue can then race.
|
||||
*/
|
||||
WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi());
|
||||
|
||||
srcu_do_enqueue(ssp, rhp, func);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(call_srcu);
|
||||
|
||||
/*
|
||||
|
|
@ -260,6 +337,14 @@ void synchronize_srcu(struct srcu_struct *ssp)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(synchronize_srcu);
|
||||
|
||||
/* Register any deferred callbacks, then wait for all in-flight ones. */
|
||||
void srcu_barrier(struct srcu_struct *ssp)
|
||||
{
|
||||
__srcu_defer_drain(ssp);
|
||||
synchronize_srcu(ssp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(srcu_barrier);
|
||||
|
||||
/*
|
||||
* get_state_synchronize_srcu - Provide an end-of-grace-period cookie
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user