rcu classic: new algorithm for callbacks-processing(v2)
RFC PATCH 2/2 rcu classic: new algorithm for callbacks-processing(v2)
This is v2, it's a little deference from v1 that I had send to lkml.
use ACCESS_ONCE
use rcu_batch_after/rcu_batch_before for batch # comparison.
rcutorture test result:
(hotplugs: do cpu-online/offline once per second)
No CONFIG_NO_HZ: OK, 12hours
No CONFIG_NO_HZ, hotplugs: OK, 12hoursCONFIG_NO_HZ=y: OK, 24hoursCONFIG_NO_HZ=y, hotplugs: Failed.
(Failed also without my patch applied, exactly the same bug occurred,
http://lkml.org/lkml/2008/7/3/24)
v1's email thread:
http://lkml.org/lkml/2008/6/2/539
v1's description:
The code/algorithm of the implement of current callbacks-processing is very efficient and technical. But when I studied it and I found a disadvantage:
In multi-CPU systems, when a new RCU callback is being queued(call_rcu[_bh]), this callback will be invoked after the grace period for the batch with batch number = rcp->cur+2 has completed very very likely in current implement. Actually, this callback can be invoked after the grace period for the batch with
batch number = rcp->cur+1 has completed. The delay of invocation means that latency of synchronize_rcu() is extended. But more important thing is that the callbacks usually free memory, and these works are delayed too! it's necessary for reclaimer to free memory as soon as possible when left memory is few.
A very simple way can solve this problem:
a field(struct rcu_head::batch) is added to record the batch number for the RCU callback. And when a new RCU callback is being queued, we determine the batch number for this callback(head->batch = rcp->cur+1) and we move this callback to rdp->donelist if we find
that head->batch <= rcp->completed when we process callbacks. This simple way reduces the wait time for invocation a lot. (about 2.5Grace Period -> 1.5Grace Period in average in multi-CPU systems)
This is my algorithm. But I do not add any field for struct rcu_head in my implement. We just need to memorize the last 2 batches and their batch number, because these 2 batches include all entries that for whom the grace period hasn't completed. So we use a special linked-list rather than add a field.
Please see the comment of struct rcu_data.
---
include/linux/rcuclassic.h +17 -9
@@ -66,11 +66,7 @@ static inline int rcu_batch_after(long a, long b)return (a - b) >0 ; } -/* -* Per-CPU data for Read-Copy UPdate. -* nxtlist - new callbacks are added here -* curlist - current batch for which quiescent cycle started if any -*/ +/* Per-CPU data for Read-Copy UPdate. */ struct rcu_data {/* 1) quiescent state handling : */ long quiescbatch;/* Batch # for grace period */
@@ -78,12 +74,24 @@ struct rcu_data {int qs_pending;/* core waits for quiesc state */ /* 2) batch handling */ -long batch;/* Batch # for current RCU batch */ +/* +* if nxtlist is not NULL, then: +* batch: +* The batch # for the last entry of nxtlist +* [*nxttail[1], NULL = *nxttail[2]): +* Entries that batch # <= batch +* [*nxttail[0], *nxttail[1]): +* Entries that batch # <= batch - 1 +* [nxtlist, *nxttail[0]): +* Entries that batch # <= batch - 2 +* The grace period for these entries has completed, and +* the other grace-period-completed entries may be moved +* here temporarily in rcu_process_callbacks(). +*/ +long batch;struct rcu_head *nxtlist; -struct rcu_head **nxttail; +struct rcu_head **nxttail[3 ];long qlen;/* # of queued callbacks */ -struct rcu_head *curlist; -struct rcu_head **curtail;struct rcu_head *donelist;struct rcu_head **donetail;long blimit;/* Upper limit on a processed batch */
kernel/rcuclassic.c +97 -60
232 lines not on this page · raw message
Quoted 8 lines · Lai Jiangshan
This is v2, it's a little deference from v1 that I had send to lkml.
useACCESS_ONCE
use rcu_batch_after/rcu_batch_before for batch # comparison.rcutorture test result:
(hotplugs: do cpu-online/offline once per second)
Quoted 6 lines
No
CONFIG_NO_HZ: OK, 12hours
NoCONFIG_NO_HZ, hotplugs: OK, 12hoursCONFIG_NO_HZ=y: OK, 24hoursCONFIG_NO_HZ=y, hotplugs: Failed.
(Failed also without my patch applied, exactly the same bug occurred,
http://lkml.org/lkml/2008/7/3/24)
thanks, i've applied this to tip/core/rcu - but it would be nice have an ack/nak from Paul as well, before we can proceed further.
Ingo
Paul E. McKenney
Acked-byQuoted 18 lines · Ingo Molnar
Quoted 8 lines · Lai Jiangshan
This is v2, it's a little deference from v1 that I had send to lkml.
useACCESS_ONCE
use rcu_batch_after/rcu_batch_before for batch # comparison.rcutorture test result:
(hotplugs: do cpu-online/offline once per second)Quoted 6 lines
No
CONFIG_NO_HZ: OK, 12hours
NoCONFIG_NO_HZ, hotplugs: OK, 12hoursCONFIG_NO_HZ=y: OK, 24hoursCONFIG_NO_HZ=y, hotplugs: Failed.
(Failed also without my patch applied, exactly the same bug occurred,
http://lkml.org/lkml/2008/7/3/24)
Quoted 2 lines
thanks, i've applied this to tip/core/rcu - but it would be nice have an ack/nak from Paul as well, before we can proceed further.
I like the general approach -- simplification is a good thing.
So, for the moment:
I need to do a more detailed review (e.g., the memory-barrier changes and the incorporation of some grace-period processing into call_rcu()), after which I will either ask for changes or give a Reviewed-by.
I need to do this more-detailed review before we ship to mainline. (Gak, already two weeks since Jiangshan sent this!!! My apologies!!!)
Thanx, Paul
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 40 lines · Paul E. McKenney
Quoted 18 lines · Ingo Molnar
Quoted 8 lines · Lai Jiangshan
This is v2, it's a little deference from v1 that I had send to lkml.
useACCESS_ONCE
use rcu_batch_after/rcu_batch_before for batch # comparison.rcutorture test result:
(hotplugs: do cpu-online/offline once per second)Quoted 6 lines
No
CONFIG_NO_HZ: OK, 12hours
NoCONFIG_NO_HZ, hotplugs: OK, 12hoursCONFIG_NO_HZ=y: OK, 24hoursCONFIG_NO_HZ=y, hotplugs: Failed.
(Failed also without my patch applied, exactly the same bug occurred,
http://lkml.org/lkml/2008/7/3/24)Quoted 2 lines
thanks, i've applied this to tip/core/rcu - but it would be nice have an ack/nak from Paul as well, before we can proceed further.
I like the general approach -- simplification is a good thing.
So, for the moment:
Acked-by Paul E. McKenney <[email protected]>I need to do a more detailed review (e.g., the memory-barrier changes and the incorporation of some grace-period processing into
call_rcu()), after which I will either ask for changes or give a Reviewed-by.I need to do this more-detailed review before we ship to mainline. (Gak, already two weeks since Jiangshan sent this!!! My apologies!!!)
diff -urpNa -X dontdiff
linux-2.6.26/include/linux/rcuclassic.hlinux-2.6.26-ljsimp/include/linux/rcuclassic.h
include/linux/rcuclassic.h line 48
Quoted 5 lines
include/linux/rcuclassic.h +1
-1@@ -45,7 +45,7 @@struct rcu_ctrlblk {long cur;/* Current batch number. */ long completed;/* Number of the last completed batch */ -int next_pending;/* Is the next batch already waiting? */ +long pending;/* Number of the last pending batch */
I vaguely remember some issue with cache locality motivating the change to a simple flag. However, if this change causes problems on large SMP machines, a better fix would be to go to a hierarchical grace-period detection algorithm. So I am OK with this change.
Quoted 63 lines
include/linux/rcuclassic.h +17
-9int signaled;@@ -66,11 +66,7 @@ static inline int rcu_batch_after(long areturn (a - b) >0 ; } -/* -* Per-CPU data for Read-Copy UPdate. -* nxtlist - new callbacks are added here -* curlist - current batch for which quiescent cycle started if any -*/ +/* Per-CPU data for Read-Copy UPdate. */ struct rcu_data {/* 1) quiescent state handling : */ long quiescbatch;/* Batch # for grace period */ @@ -78,12 +74,24 @@ struct rcu_data {int qs_pending;/* core waits for quiesc state */ /* 2) batch handling */ -long batch;/* Batch # for current RCU batch */ +/* +* if nxtlist is not NULL, then: +* batch: +* The batch # for the last entry of nxtlist +* [*nxttail[1], NULL = *nxttail[2]): +* Entries that batch # <= batch +* [*nxttail[0], *nxttail[1]): +* Entries that batch # <= batch - 1 +* [nxtlist, *nxttail[0]): +* Entries that batch # <= batch - 2 +* The grace period for these entries has completed, and +* the other grace-period-completed entries may be moved +* here temporarily in rcu_process_callbacks(). +*/ +long batch;struct rcu_head *nxtlist; -struct rcu_head **nxttail; +struct rcu_head **nxttail[3 ];long qlen;/* # of queued callbacks */ -struct rcu_head *curlist; -struct rcu_head **curtail;struct rcu_head *donelist;struct rcu_head **donetail;long blimit;/* Upper limit on a processed batch */ diff -urpNa -X dontdiff
linux-2.6.26/kernel/rcuclassic.clinux-2.6.26-ljsimp/kernel/rcuclassic.ckernel/rcuclassic.c +2
-0@@ -60,12 +60,14 @@ EXPORT_SYMBOL_GPL(rcu_lock_map);static struct rcu_ctrlblk rcu_ctrlblk = { .cur =-300 , .completed =-300 , + .pending =-300 , .lock =__SPIN_LOCK_UNLOCKED (&rcu_ctrlblk.lock), .cpumask =CPU_MASK_NONE , };static struct rcu_ctrlblk rcu_bh_ctrlblk = { .cur =-300 , .completed =-300 , + .pending =-300 , .lock =__SPIN_LOCK_UNLOCKED (&rcu_bh_ctrlblk.lock), .cpumask =CPU_MASK_NONE , };
kernel/rcuclassic.c static inline void force_quiescent_state · line 127
Quoted 8 lines
kernel/rcuclassic.c +5
-0@@ -118,6 +120,43 @@ static inline void force_quiescent_state }#endif +static void __call_rcu (struct rcu_head *head,struct rcu_ctrlblk *rcp, +struct rcu_data *rdp) +{ +long batch; +smp_mb ();/* reads the most recently updated value of rcu->cur. */
A better comment would be something like:
smb_mb(); /* Read of rcu->cur must happen after any change by caller. */
Quoted 36 lines
kernel/rcuclassic.c +32
-0+ +/* +* Determine the batch number of this callback. +* +* Using ACCESS_ONCE to avoid the following error when gcc eliminates +* local variable "batch" and emits codes like this: +* 1) rdp->batch = rcp->cur + 1 # gets old value +* ...... +* 2)rcu_batch_after(rcp->cur + 1, rdp->batch) # gets new value +* then [*nxttail[0], *nxttail[1]) may contain callbacks +* that batch# = rdp->batch, see the comment of struct rcu_data. +*/ + batch =ACCESS_ONCE (rcp->cur) +1 ; + +if (rdp->nxtlist &&rcu_batch_after (batch, rdp->batch)) { +/* process callbacks */ + rdp->nxttail[0 ] = rdp->nxttail[1 ]; + rdp->nxttail[1 ] = rdp->nxttail[2 ]; +if (rcu_batch_after (batch -1 , rdp->batch)) + rdp->nxttail[0 ] = rdp->nxttail[2 ]; + } + + rdp->batch = batch; + *rdp->nxttail[2 ] = head; + rdp->nxttail[2 ] = &head->next; + +if (unlikely (++rdp->qlen > qhimark)) { + rdp->blimit =INT_MAX ; +force_quiescent_state (rdp, &rcu_ctrlblk); + } +} +/** * call_rcu - Queue an RCU callback for invocation after a grace period. * @head: structure to be used for queueing the RCU updates.
kernel/rcuclassic.c void call_rcu(struct rcu_head *head, · line 178
Quoted 8 lines
kernel/rcuclassic.c +0
-1@@ -133,18 +172,11 @@ void call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) {unsigned long flags; -struct rcu_data *rdp; head->func = func; head->next =NULL ;local_irq_save (flags);
I very much like the gathering of common code from call_rcu() and call_rcu_bh() into __call_rcu(). But why not also move the local_irq_save() and local_irq_restore() to __call_rcu(), perhaps along with the initialization of head->next?
(I understand the motivation for keeping the initialization of the fields of "head" at this level -- otherwise, you must add another argument to __call_rcu(). But might be worth considering...)
Quoted 34 lines
kernel/rcuclassic.c +2
-17- rdp = &__get_cpu_var (rcu_data); - *rdp->nxttail = head; - rdp->nxttail = &head->next; -if (unlikely (++rdp->qlen > qhimark)) { - rdp->blimit =INT_MAX ; -force_quiescent_state (rdp, &rcu_ctrlblk); - } +__call_rcu (head, &rcu_ctrlblk, &__get_cpu_var (rcu_data));local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu);@@ -169,20 +201,11 @@ void call_rcu_bh(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) {unsigned long flags; -struct rcu_data *rdp; head->func = func; head->next =NULL ;local_irq_save (flags); - rdp = &__get_cpu_var (rcu_bh_data); - *rdp->nxttail = head; - rdp->nxttail = &head->next; - -if (unlikely (++rdp->qlen > qhimark)) { - rdp->blimit =INT_MAX ; -force_quiescent_state (rdp, &rcu_bh_ctrlblk); - } - +__call_rcu (head, &rcu_bh_ctrlblk, &__get_cpu_var (rcu_bh_data));local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu_bh);
kernel/rcuclassic.c EXPORT_SYMBOL_GPL(rcu_batches_completed_ · line 219
Quoted 9 lines
kernel/rcuclassic.c +0
-6@@ -211,12 +234,6 @@ EXPORT_SYMBOL_GPL(rcu_batches_completed_static inline void raise_rcu_softirq (void ) {raise_softirq (RCU_SOFTIRQ ); -/* -* The smp_mb() here is required to ensure that this cpu's -* __rcu_process_callbacks() reads the most recently updated -* value of rcu->cur. -*/ -smp_mb ();
I have not yet convinced myself that it is OK to get rid of this memory barrier. This memory barrier was intended order to handle the following sequence of events:
rcu_read_lock_bh(); /* no memory barrier. */ p = rcu_dereference(some_global_pointer); do_something_with(p); rcu_read_unlock_bh(); /* no memory barrier. */ ---- scheduling-clock interrupt occurs, eventually invoking ---- rcu_check_callbacks() ---- And the access to "p" above could potentially be reordered ---- into the grace-period computation
Such reordering is of course quite unlikely to be harmful, due to the long duration of RCU grace periods. But I am paranoid.
If this memory barrier turns out to be necessary, one approach would to be to place it at the beginning of rcu_check_callbacks(), which is a better place for it in any case.
Thoughts?
Quoted 6 lines
kernel/rcuclassic.c +0
-0}/* @@ -276,14 +293,8 @@ static void rcu_do_batch(struct rcu_data*/ static void rcu_start_batch (struct rcu_ctrlblk *rcp)
kernel/rcuclassic.c static void rcu_do_batch(struct rcu_data · line 286
Quoted 10 lines
kernel/rcuclassic.c +1
-7{ -if (rcp->next_pending && +if (rcp->cur != rcp->pending && rcp->completed == rcp->cur) { - rcp->next_pending =0 ; -/* -* next_pending == 0 must be visible in -* __rcu_process_callbacks() before it can see new value of cur. -*/ -smp_wmb ();
These memory barriers were added to allow for the fact that we run much of the grace-period machinery without holding locks.
Quoted 26 lines
kernel/rcuclassic.c +8
-6rcp->cur++;/* @@ -364,13 +375,15 @@ static void rcu_check_quiescent_state(st* which is dead and hence not processing interrupts. */ static void rcu_move_batch (struct rcu_data *this_rdp,struct rcu_head *list, -struct rcu_head **tail) +struct rcu_head **tail,long batch) { -local_irq_disable (); - *this_rdp->nxttail = list; -if (list) - this_rdp->nxttail = tail; -local_irq_enable (); +if (list) { +local_irq_disable (); + this_rdp->batch = batch; + *this_rdp->nxttail[2 ] = list; + this_rdp->nxttail[2 ] = tail; +local_irq_enable (); + } } static void __rcu_offline_cpu(struct rcu_data *this_rdp,
kernel/rcuclassic.c static void __rcu_offline_cpu(struct rcu · line 400
Quoted 7 lines
kernel/rcuclassic.c +1
-3@@ -384,9 +397,9 @@ static void __rcu_offline_cpu(struct rcuif (rcp->cur != rcp->completed)cpu_quiet (rdp->cpu, rcp);spin_unlock_bh (&rcp->lock); -rcu_move_batch (this_rdp, rdp->donelist, rdp->donetail); -rcu_move_batch (this_rdp, rdp->curlist, rdp->curtail); -rcu_move_batch (this_rdp, rdp->nxtlist, rdp->nxttail); +/* spin_lock implies smp_mb() */
Almost. Please note that spin_lock() and spin_unlock() are permitted to imply semi-permeable memory barriers. See the ia64 implementation for an example of this. The upshot is that spin_lock() is permitted to allow prior memory references to "bleed" into the critical section, and spin_unlock() is likewise permitted to allow subsequent memory references to "bleed" into the critical section. So, if you have code as follows:
a = 1; spin_lock(&my_lock); b = 1; c = 1; spin_unlock(&my_lock); d = 1;
Then the following is a perfectly legal execution ordering:
spin_lock(&my_lock); d = 1; c = 1; b = 1; a = 1; spin_unlock(&my_lock);
Not sure if this causes the code any problems, but at the very least, the comment needs to change.
more later...
Thanx, Paul
Quoted 127 lines
kernel/rcuclassic.c +53
-33+rcu_move_batch (this_rdp, rdp->donelist, rdp->donetail, rcp->cur +1 ); +rcu_move_batch (this_rdp, rdp->nxtlist, rdp->nxttail[2 ], rcp->cur +1 ); }static void rcu_offline_cpu (int cpu)@@ -416,37 +429,45 @@ static void rcu_offline_cpu(int cpu)static void __rcu_process_callbacks (struct rcu_ctrlblk *rcp,struct rcu_data *rdp) { -if (rdp->curlist && !rcu_batch_before (rcp->completed, rdp->batch)) { - *rdp->donetail = rdp->curlist; - rdp->donetail = rdp->curtail; - rdp->curlist =NULL ; - rdp->curtail = &rdp->curlist; - } - -if (rdp->nxtlist && !rdp->curlist) { +if (rdp->nxtlist) {local_irq_disable (); - rdp->curlist = rdp->nxtlist; - rdp->curtail = rdp->nxttail; - rdp->nxtlist =NULL ; - rdp->nxttail = &rdp->nxtlist; -local_irq_enable ();/* -* start the next batch of callbacks +* move the other grace-period-completed entries to +* [rdp->nxtlist, *rdp->nxttail[0]) temporarily */ +if (!rcu_batch_before (rcp->completed, rdp->batch)) + rdp->nxttail[0 ] = rdp->nxttail[1 ] = rdp->nxttail[2 ]; +else if (!rcu_batch_before (rcp->completed, rdp->batch -1 )) + rdp->nxttail[0 ] = rdp->nxttail[1 ]; -/* determine batch number */ - rdp->batch = rcp->cur +1 ; -/* see the comment and corresponding wmb() in -* the rcu_start_batch() +/* +* the grace period for entries in +* [rdp->nxtlist, *rdp->nxttail[0]) has completed and +* move these entries to donelist */ -smp_rmb (); +if (rdp->nxttail[0 ] != &rdp->nxtlist) { + *rdp->donetail = rdp->nxtlist; + rdp->donetail = rdp->nxttail[0 ]; + rdp->nxtlist = *rdp->nxttail[0 ]; + *rdp->donetail =NULL ; + +if (rdp->nxttail[1 ] == rdp->nxttail[0 ]) + rdp->nxttail[1 ] = &rdp->nxtlist; +if (rdp->nxttail[2 ] == rdp->nxttail[0 ]) + rdp->nxttail[2 ] = &rdp->nxtlist; + rdp->nxttail[0 ] = &rdp->nxtlist; + } + +local_irq_enable (); -if (!rcp->next_pending) { +if (rcu_batch_after (rdp->batch, rcp->pending)) {/* and start it/schedule start if it's a new batch */ spin_lock (&rcp->lock); - rcp->next_pending =1 ; -rcu_start_batch (rcp); +if (rcu_batch_after (rdp->batch, rcp->pending)) { + rcp->pending = rdp->batch; +rcu_start_batch (rcp); + }spin_unlock (&rcp->lock); } }@@ -464,15 +485,26 @@ static void rcu_process_callbacks(structstatic int __rcu_pending (struct rcu_ctrlblk *rcp,struct rcu_data *rdp) { -/* This cpu has pending rcu entries and the grace period -* for them has completed. -*/ -if (rdp->curlist && !rcu_batch_before (rcp->completed, rdp->batch)) -return 1 ; +if (rdp->nxtlist) { +/* +* This cpu has pending rcu entries and the grace period +* for them has completed. +*/ +if (!rcu_batch_before (rcp->completed, rdp->batch)) +return 1 ; +if (!rcu_batch_before (rcp->completed, rdp->batch -1 ) && + rdp->nxttail[0 ] != rdp->nxttail[1 ]) +return 1 ; +if (rdp->nxttail[0 ] != &rdp->nxtlist) +return 1 ; -/* This cpu has no pending entries, but there are new entries */ -if (!rdp->curlist && rdp->nxtlist) -return 1 ; +/* +* This cpu has pending rcu entries and the new batch +* for then hasn't been started nor scheduled start +*/ +if (rcu_batch_after (rdp->batch, rcp->pending)) +return 1 ; + }/* This cpu has finished callbacks to invoke */ if (rdp->donelist)@@ -508,7 +540,7 @@ int rcu_needs_cpu(int cpu)struct rcu_data *rdp = &per_cpu (rcu_data, cpu);struct rcu_data *rdp_bh = &per_cpu (rcu_bh_data, cpu); -return (!!rdp->curlist || !!rdp_bh->curlist ||rcu_pending (cpu)); +return !!rdp->nxtlist || !!rdp_bh->nxtlist ||rcu_pending (cpu); }void rcu_check_callbacks (int cpu,int user)@@ -527,8 +559,7 @@ static void rcu_init_percpu_data(int cpu struct rcu_data *rdp) {memset (rdp,0 ,sizeof (*rdp)); - rdp->curtail = &rdp->curlist; - rdp->nxttail = &rdp->nxtlist; + rdp->nxttail[0 ] = rdp->nxttail[1 ] = rdp->nxttail[2 ] = &rdp->nxtlist; rdp->donetail = &rdp->donelist; rdp->quiescbatch = rcp->completed; rdp->qs_pending =0 ;
Paul E. McKenney wrote:
[...]
Quoted 9 lines
Quoted 9 lines
/**
*call_rcu- Queue an RCU callback for invocation after a grace period.
* @head: structure to be used for queueing the RCU updates.kernel/rcuclassic.c +0
-1@@ -133,18 +172,11 @@ void call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) {unsigned long flags; -struct rcu_data *rdp;
Quoted 8 lines
kernel/rcuclassic.c void call_rcu(struct rcu_head *head, · line 178
Quoted 3 lines
kernel/rcuclassic.c +0
-0head->func = func; head->next =NULL ;local_irq_save (flags);I very much like the gathering of common code from
call_rcu()andcall_rcu_bh()into__call_rcu(). But why not also move thelocal_irq_save()andlocal_irq_restore()to__call_rcu(), perhaps along with the initialization ofhead->next?
We should put __get_cpu_var into preempt_disable critical section. So I didn't move the local_irq_save() and local_irq_restore() to __call_rcu().
I greed your changes except the changes here.percpu_ptr() may help for us.
Quoted 64 lines
(I understand the motivation for keeping the initialization of the fields of "head" at this level -- otherwise, you must add another argument to
__call_rcu(). But might be worth considering...)Quoted 34 lines
kernel/rcuclassic.c +2
-17- rdp = &__get_cpu_var (rcu_data); - *rdp->nxttail = head; - rdp->nxttail = &head->next; -if (unlikely (++rdp->qlen > qhimark)) { - rdp->blimit =INT_MAX ; -force_quiescent_state (rdp, &rcu_ctrlblk); - } +__call_rcu (head, &rcu_ctrlblk, &__get_cpu_var (rcu_data));local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu);@@ -169,20 +201,11 @@ void call_rcu_bh(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) {unsigned long flags; -struct rcu_data *rdp; head->func = func; head->next =NULL ;local_irq_save (flags); - rdp = &__get_cpu_var (rcu_bh_data); - *rdp->nxttail = head; - rdp->nxttail = &head->next; - -if (unlikely (++rdp->qlen > qhimark)) { - rdp->blimit =INT_MAX ; -force_quiescent_state (rdp, &rcu_bh_ctrlblk); - } - +__call_rcu (head, &rcu_bh_ctrlblk, &__get_cpu_var (rcu_bh_data));local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu_bh);kernel/rcuclassic.c EXPORT_SYMBOL_GPL(rcu_batches_completed_ · line 219
Quoted 9 lines
kernel/rcuclassic.c +0
-6@@ -211,12 +234,6 @@ EXPORT_SYMBOL_GPL(rcu_batches_completed_static inline void raise_rcu_softirq (void ) {raise_softirq (RCU_SOFTIRQ ); -/* -* The smp_mb() here is required to ensure that this cpu's -* __rcu_process_callbacks() reads the most recently updated -* value of rcu->cur. -*/ -smp_mb ();I have not yet convinced myself that it is OK to get rid of this memory barrier. This memory barrier was intended order to handle the following sequence of events:
rcu_read_lock_bh(); /* no memory barrier. */ p = rcu_dereference(some_global_pointer); do_something_with(p); rcu_read_unlock_bh(); /* no memory barrier. */ ---- scheduling-clock interrupt occurs, eventually invoking ---- rcu_check_callbacks() ---- And the access to "p" above could potentially be reordered ---- into the grace-period computation
Quoted 8 lines
Such reordering is of course quite unlikely to be harmful, due to the long duration of RCU grace periods. But I am paranoid.
If this memory barrier turns out to be necessary, one approach would to be to place it at the beginning of
rcu_check_callbacks(), which is a better place for it in any case.Thoughts?
I hasn't thought it before. I thought that smp_mb is used for rcu->cur as the original comment had told.
I prefer to add memory barrier to rcu_process_callbacks as your patch.
But I has a question here:
In this case, p->rcu_head is not in donelist. So __rcu_process_callbacks is only access to p->rcu_head(p->rcu_head.next). And other cpus don't access to p->rcu_head which has been queued on this cpu' rcu_data.
Is this reordering harmful(How this reordering make other cpus' access wrong)?
[...]
Quoted 22 lines · Lai Jiangshan
Paul E. McKenney wrote:
[...]
Quoted 9 lines
Quoted 9 lines
/**
*call_rcu- Queue an RCU callback for invocation after a grace period.
* @head: structure to be used for queueing the RCU updates.kernel/rcuclassic.c +0
-1@@ -133,18 +172,11 @@ void call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) {unsigned long flags; -struct rcu_data *rdp;Quoted 8 lines
kernel/rcuclassic.c void call_rcu(struct rcu_head *head, · line 178
Quoted 3 lines
kernel/rcuclassic.c +0
-0head->func = func; head->next =NULL ;local_irq_save (flags);I very much like the gathering of common code from
call_rcu()andcall_rcu_bh()into__call_rcu(). But why not also move thelocal_irq_save()andlocal_irq_restore()to__call_rcu(), perhaps along with the initialization ofhead->next?
Quoted 3 lines
We should put
__get_cpu_varintopreempt_disablecritical section. So I didn't move thelocal_irq_save()andlocal_irq_restore()to__call_rcu().
Good point -- a preemption just at the call to __call_rcu() does need to be handled correctly. I will update the patch.
Quoted 2 lines
I greed your changes except the changes here.
percpu_ptr()may help for us.
Tell me more about percpu_ptr().
Quoted 70 lines
Quoted 70 lines
(I understand the motivation for keeping the initialization of the fields of "head" at this level -- otherwise, you must add another argument to
__call_rcu(). But might be worth considering...)Quoted 34 lines
kernel/rcuclassic.c +2
-17- rdp = &__get_cpu_var (rcu_data); - *rdp->nxttail = head; - rdp->nxttail = &head->next; -if (unlikely (++rdp->qlen > qhimark)) { - rdp->blimit =INT_MAX ; -force_quiescent_state (rdp, &rcu_ctrlblk); - } +__call_rcu (head, &rcu_ctrlblk, &__get_cpu_var (rcu_data));local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu);@@ -169,20 +201,11 @@ void call_rcu_bh(struct rcu_head *head, void (*func)(struct rcu_head *rcu)) {unsigned long flags; -struct rcu_data *rdp; head->func = func; head->next =NULL ;local_irq_save (flags); - rdp = &__get_cpu_var (rcu_bh_data); - *rdp->nxttail = head; - rdp->nxttail = &head->next; - -if (unlikely (++rdp->qlen > qhimark)) { - rdp->blimit =INT_MAX ; -force_quiescent_state (rdp, &rcu_bh_ctrlblk); - } - +__call_rcu (head, &rcu_bh_ctrlblk, &__get_cpu_var (rcu_bh_data));local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu_bh);kernel/rcuclassic.c EXPORT_SYMBOL_GPL(rcu_batches_completed_ · line 219
Quoted 9 lines
kernel/rcuclassic.c +0
-6@@ -211,12 +234,6 @@ EXPORT_SYMBOL_GPL(rcu_batches_completed_static inline void raise_rcu_softirq (void ) {raise_softirq (RCU_SOFTIRQ ); -/* -* The smp_mb() here is required to ensure that this cpu's -* __rcu_process_callbacks() reads the most recently updated -* value of rcu->cur. -*/ -smp_mb ();I have not yet convinced myself that it is OK to get rid of this memory barrier. This memory barrier was intended order to handle the following sequence of events:
rcu_read_lock_bh(); /* no memory barrier. */ p = rcu_dereference(some_global_pointer); do_something_with(p); rcu_read_unlock_bh(); /* no memory barrier. */ ---- scheduling-clock interrupt occurs, eventually invoking ---- rcu_check_callbacks() ---- And the access to "p" above could potentially be reordered ---- into the grace-period computationSuch reordering is of course quite unlikely to be harmful, due to the long duration of RCU grace periods. But I am paranoid.
If this memory barrier turns out to be necessary, one approach would to be to place it at the beginning of
rcu_check_callbacks(), which is a better place for it in any case.
Quoted 6 lines
Quoted 1 line
Thoughts?
I hasn't thought it before. I thought that
smp_mbis used forrcu->curas the original comment had told.I prefer to add memory barrier to
rcu_process_callbacksas your patch.
Yeah, that became clear as I wrote the code. ;-)
Quoted 8 lines
But I has a question here:
In this case,
p->rcu_headis not in donelist. So__rcu_process_callbacksis only access top->rcu_head(p->rcu_head.next). And other cpus don't access top->rcu_headwhich has been queued on this cpu'rcu_data.Is this reordering harmful(How this reordering make other cpus' access wrong)?
I have a somewhat different goal here. I want to simplify the memory ordering design without giving up too much performance -- the current state in mainline is much too fragile, in my opinion, especially given that the grace-period code paths are not fastpaths.
Next step -- hierarchical grace-period detection to handle the 4096-CPU machines that I was being buttonholed about at OLS...
Would you be interested in applying your multi-tailed list change to preemptable RCU?
Thanx, Paul
Quoted 9 lines
[...]
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Paul E. McKenney wrote:
[...]
Quoted 2 lines
Tell me more about
percpu_ptr().
Sorry about this. percpu_ptr is used for dynamic allocation percpu pointer.
It seems that we cannot get a pointer from a static declare percpu data which can be used as a dynamic allocation percpu data's pointer.
Quoted 1 line
[...]
Quoted 6 lines
I have a somewhat different goal here. I want to simplify the memory ordering design without giving up too much performance -- the current state in mainline is much too fragile, in my opinion, especially given that the grace-period code paths are not fastpaths.
Quoted 6 lines
Next step -- hierarchical grace-period detection to handle the 4096-CPU machines that I was being buttonholed about at OLS...
Would you be interested in applying your multi-tailed list change to preemptable RCU?
It's not necessary. Actually I like one tail per list which is good for readability.
But in my patch, the most work is combining lists, not moving a list to next list, so i use multi-tailed simplify this works and others(etc: "if (rdp->nxtlist)" will be changed to be a more complex and less readability statement if i use one-tail-per-list)
These not means multi-tailed is good thing.
Quoted 6 lines · Lai Jiangshan
Paul E. McKenney wrote:
[...]Quoted 2 lines
Tell me more about
percpu_ptr().Sorry about this.
percpu_ptris used for dynamic allocation percpu pointer.
Yep, that I knew.
Quoted 2 lines
It seems that we cannot get a pointer from a static declare percpu data which can be used as a dynamic allocation percpu data's pointer.
Sad but true... Ran into this with SRCU a couple of years back. :-/
Quoted 17 lines
Quoted 1 line
[...]
Quoted 6 lines
I have a somewhat different goal here. I want to simplify the memory ordering design without giving up too much performance -- the current state in mainline is much too fragile, in my opinion, especially given that the grace-period code paths are not fastpaths.
Quoted 6 lines
Next step -- hierarchical grace-period detection to handle the 4096-CPU machines that I was being buttonholed about at OLS...
Would you be interested in applying your multi-tailed list change to preemptable RCU?
It's not necessary. Actually I like one tail per list which is good for readability.
Quoted 6 lines
But in my patch, the most work is combining lists, not moving a list to next list, so i use multi-tailed simplify this works and others(etc: "if (
rdp->nxtlist)" will be changed to be a more complex and less readability statement if i use one-tail-per-list)These not means multi-tailed is good thing.
It does indeed depend on the details of the implementation.
Thanx, Paul
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 27 lines · Paul E. McKenney
Quoted 26 lines · Paul E. McKenney
Quoted 18 lines · Ingo Molnar
Quoted 8 lines · Lai Jiangshan
This is v2, it's a little deference from v1 that I had send to lkml.
useACCESS_ONCE
use rcu_batch_after/rcu_batch_before for batch # comparison.rcutorture test result:
(hotplugs: do cpu-online/offline once per second)Quoted 6 lines
No
CONFIG_NO_HZ: OK, 12hours
NoCONFIG_NO_HZ, hotplugs: OK, 12hoursCONFIG_NO_HZ=y: OK, 24hoursCONFIG_NO_HZ=y, hotplugs: Failed.
(Failed also without my patch applied, exactly the same bug occurred,
http://lkml.org/lkml/2008/7/3/24)Quoted 2 lines
thanks, i've applied this to tip/core/rcu - but it would be nice have an ack/nak from Paul as well, before we can proceed further.
I like the general approach -- simplification is a good thing.
So, for the moment:
Quoted 8 lines
Quoted 8 lines
Acked-by Paul E. McKenney <[email protected]>I need to do a more detailed review (e.g., the memory-barrier changes and the incorporation of some grace-period processing into
call_rcu()), after which I will either ask for changes or give a Reviewed-by.I need to do this more-detailed review before we ship to mainline. (Gak, already two weeks since Jiangshan sent this!!! My apologies!!!)
And here is a patch to apply on top of Jiangshan's patch that rationalizes the locking and memory barriers. Much of this is general cleanup, not necessarily directly related to Jiangshan's patch. Given this as a base, it should be fairly easy to make a hierarchical implementation for machines that see lock contention in the RCU infrastructure -- split and make a hierarchy on rcu_ctrlblk.
Thoughts?
---
rcuclassic.c | 62 +++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 18 deletions(-)
kernel/rcuclassic.c +44 -18
@@ -86,6 +86,7 @@ static void force_quiescent_state(structint cpu;cpumask_t cpumask;set_need_resched (); +spin_lock (&rcp->lock);if (unlikely (!rcp->signaled)) { rcp->signaled =1 ;/*
@@ -111,6 +112,7 @@ static void force_quiescent_state(structfor_each_cpu_mask (cpu, cpumask) smp_send_reschedule(cpu); } +spin_unlock (&rcp->lock); }#else static inline void force_quiescent_state(struct rcu_data *rdp,
@@ -124,7 +126,11 @@ static void __call_rcu(struct rcu_head * struct rcu_data *rdp) {long batch; -smp_mb ();/* reads the most recently updated value of rcu->cur. */ +unsigned long flags; + + head->next =NULL ; +local_irq_save (flags); +smp_mb ();/* Read of rcu->cur must happen after any change by caller. */ /* * Determine the batch number of this callback.
@@ -155,6 +161,7 @@ static void __call_rcu(struct rcu_head * rdp->blimit =INT_MAX ;force_quiescent_state (rdp, &rcu_ctrlblk); } +local_irq_restore (flags); }/**
@@ -171,13 +178,8 @@ static void __call_rcu(struct rcu_head *void call_rcu (struct rcu_head *head,void (*func)(struct rcu_head *rcu)) { -unsigned long flags; - head->func = func; - head->next =NULL ; -local_irq_save (flags);__call_rcu (head, &rcu_ctrlblk, &__get_cpu_var (rcu_data)); -local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu);
@@ -200,13 +202,8 @@ EXPORT_SYMBOL_GPL(call_rcu);void call_rcu_bh (struct rcu_head *head,void (*func)(struct rcu_head *rcu)) { -unsigned long flags; - head->func = func; - head->next =NULL ; -local_irq_save (flags);__call_rcu (head, &rcu_bh_ctrlblk, &__get_cpu_var (rcu_bh_data)); -local_irq_restore (flags); }EXPORT_SYMBOL_GPL (call_rcu_bh);
@@ -389,17 +386,17 @@ static void rcu_move_batch(struct rcu_dastatic void __rcu_offline_cpu (struct rcu_data *this_rdp,struct rcu_ctrlblk *rcp,struct rcu_data *rdp) { -/* if the cpu going offline owns the grace period +/* +* if the cpu going offline owns the grace period * we can block indefinitely waiting for it, so flush * it here */ spin_lock_bh (&rcp->lock);if (rcp->cur != rcp->completed)cpu_quiet (rdp->cpu, rcp); -spin_unlock_bh (&rcp->lock); -/* spin_lock implies smp_mb() */ rcu_move_batch (this_rdp, rdp->donelist, rdp->donetail, rcp->cur +1 );rcu_move_batch (this_rdp, rdp->nxtlist, rdp->nxttail[2 ], rcp->cur +1 ); +spin_unlock_bh (&rcp->lock); }static void rcu_offline_cpu (int cpu)
@@ -429,16 +426,19 @@ static void rcu_offline_cpu(int cpu)static void __rcu_process_callbacks (struct rcu_ctrlblk *rcp,struct rcu_data *rdp) { +long completed_snap; +if (rdp->nxtlist) {local_irq_disable (); + completed_snap =ACCESS_ONCE (rcp->completed);/* * move the other grace-period-completed entries to * [rdp->nxtlist, *rdp->nxttail[0]) temporarily */ -if (!rcu_batch_before (rcp->completed, rdp->batch)) +if (!rcu_batch_before (completed_snap, rdp->batch)) rdp->nxttail[0 ] = rdp->nxttail[1 ] = rdp->nxttail[2 ]; -else if (!rcu_batch_before (rcp->completed, rdp->batch -1 )) +else if (!rcu_batch_before (completed_snap, rdp->batch -1 )) rdp->nxttail[0 ] = rdp->nxttail[1 ];/*
@@ -479,20 +479,38 @@ static void __rcu_process_callbacks(strustatic void rcu_process_callbacks (struct softirq_action *unused) { +/* +* Memory references from any prior RCU read-side critical sections +* executed by the interrupted code must be see before any RCU +* grace-period manupulations below. +*/ + +smp_mb ();/* See above block comment. */ +__rcu_process_callbacks (&rcu_ctrlblk, &__get_cpu_var (rcu_data));__rcu_process_callbacks (&rcu_bh_ctrlblk, &__get_cpu_var (rcu_bh_data)); + +/* +* Memory references from any later RCU read-side critical sections +* executed by the interrupted code must be see after any RCU +* grace-period manupulations above. +*/ + +smp_mb ();/* See above block comment. */ }static int __rcu_pending (struct rcu_ctrlblk *rcp,struct rcu_data *rdp) {if (rdp->nxtlist) { +long completed_snap =ACCESS_ONCE (rcp->completed); +/* * This cpu has pending rcu entries and the grace period * for them has completed. */ -if (!rcu_batch_before (rcp->completed, rdp->batch)) +if (!rcu_batch_before (completed_snap, rdp->batch))return 1 ; -if (!rcu_batch_before (rcp->completed, rdp->batch -1 ) && +if (!rcu_batch_before (completed_snap, rdp->batch -1 ) && rdp->nxttail[0 ] != rdp->nxttail[1 ])return 1 ;if (rdp->nxttail[0 ] != &rdp->nxtlist)
@@ -543,6 +561,12 @@ int rcu_needs_cpu(int cpu)return !!rdp->nxtlist || !!rdp_bh->nxtlist ||rcu_pending (cpu); } +/* +* Top-level function driving RCU grace-period detection, normally +* invoked from the scheduler-clock interrupt. This function simply +* increments counters that are read only from softirq by this same +* CPU, so there are no memory barriers required. +*/ void rcu_check_callbacks (int cpu,int user) { if (user ||
@@ -558,6 +582,7 @@ void rcu_check_callbacks(int cpu, int usstatic void rcu_init_percpu_data (int cpu,struct rcu_ctrlblk *rcp,struct rcu_data *rdp) { +spin_lock (&rcp->lock);memset (rdp,0 ,sizeof (*rdp)); rdp->nxttail[0 ] = rdp->nxttail[1 ] = rdp->nxttail[2 ] = &rdp->nxtlist; rdp->donetail = &rdp->donelist;
@@ -565,6 +590,7 @@ static void rcu_init_percpu_data(int cpu rdp->qs_pending =0 ; rdp->cpu = cpu; rdp->blimit = blimit; +spin_unlock (&rcp->lock); }static void __cpuinit rcu_online_cpu (int cpu)