From: "Paul E. McKenney" <paulmck@linux.ibm.com>
To: Junchang Wang <junchangwang@gmail.com>
Cc: akiyks@gmail.com, perfbook@vger.kernel.org
Subject: Re: [PATCH] rcu_nest: Update description of rcu_nest.[hc]
Date: Wed, 29 May 2019 06:30:41 -0700 [thread overview]
Message-ID: <20190529133041.GY28207@linux.ibm.com> (raw)
In-Reply-To: <1559105262-6502-1-git-send-email-junchangwang@gmail.com>
On Wed, May 29, 2019 at 12:47:42PM +0800, Junchang Wang wrote:
> Commit 61e0eb9cdb89 ("rcu_nest: Fix concurrency issues") fixes a few
> concurrency issues in rcu_nest.h and rcu_nest.c. This patch accordingly
> updates the description in Appendix B Toy RCU Implementations. Besides, the
> new scheme, which directly extracts snippet from rcu_nest.[hc], is applied.
>
>
> Signed-off-by: Junchang Wang <junchangwang@gmail.com>
Applied and pushed, thank you, Junchang!
Thanx, Paul
> ---
>
> Thanks again for writing and sharing perfbook. I can always find great
> pleasure in reading through a chapter of this book :-).
>
> CodeSamples/defer/rcu_nest.c | 35 ++++++++------
> CodeSamples/defer/rcu_nest.h | 36 +++++++++------
> appendix/toyrcu/toyrcu.tex | 108 +++++++++++++++----------------------------
> 3 files changed, 81 insertions(+), 98 deletions(-)
>
> diff --git a/CodeSamples/defer/rcu_nest.c b/CodeSamples/defer/rcu_nest.c
> index 362f466..7e36866 100644
> --- a/CodeSamples/defer/rcu_nest.c
> +++ b/CodeSamples/defer/rcu_nest.c
> @@ -21,45 +21,52 @@
> #include "../api.h"
> #include "rcu_nest.h"
>
> -void synchronize_rcu(void)
> +//\begin{snippet}[labelbase=ln:defer:rcu_nest:synchronize,gobbleblank=yes,commandchars=\%\@\$]
> +void synchronize_rcu(void) //\lnlbl{syn:b}
> {
> int t;
> -
> + //\fcvblank
> /* Memory barrier ensures mutation seen before grace period. */
>
> - smp_mb();
> + smp_mb(); //\lnlbl{syn:mb1}
>
> /* Only one synchronize_rcu() at a time. */
>
> - spin_lock(&rcu_gp_lock);
> + spin_lock(&rcu_gp_lock); //\lnlbl{syn:spinlock}
>
> /* Advance to a new grace-period number, enforce ordering. */
>
> - WRITE_ONCE(rcu_gp_ctr, rcu_gp_ctr + RCU_GP_CTR_BOTTOM_BIT);
> - smp_mb();
> + WRITE_ONCE(rcu_gp_ctr, rcu_gp_ctr + //\lnlbl{syn:incgp1}
> + RCU_GP_CTR_BOTTOM_BIT); //\lnlbl{syn:incgp2}
> + smp_mb(); //\lnlbl{syn:mb2}
>
> /*
> * Wait until all threads are either out of their RCU read-side
> * critical sections or are aware of the new grace period.
> */
>
> - for_each_thread(t) {
> - while (rcu_gp_ongoing(t) &&
> - ((READ_ONCE(per_thread(rcu_reader_gp, t)) -
> - rcu_gp_ctr) < 0)) {
> + for_each_thread(t) { //\lnlbl{syn:scan:b}
> + while (rcu_gp_ongoing(t) && //\lnlbl{syn:ongoing}
> + ((READ_ONCE(per_thread(rcu_reader_gp, t)) -//\lnlbl{syn:lt1}
> + rcu_gp_ctr) < 0)) { //\lnlbl{syn:lt2}
> +#ifndef FCV_SNIPPET
> /*@@@ poll(NULL, 0, 10); */
> barrier();
> +#else
> + poll(NULL, 0, 10); //\lnlbl{syn:poll}
> +#endif
> }
> - }
> + } //\lnlbl{syn:scan:e}
>
> /* Let other synchronize_rcu() instances move ahead. */
>
> - spin_unlock(&rcu_gp_lock);
> + spin_unlock(&rcu_gp_lock); //\lnlbl{syn:spinunlock}
>
> /* Ensure that any subsequent free()s happen -after- above checks. */
>
> - smp_mb();
> -}
> + smp_mb(); //\lnlbl{syn:mb3}
> +} //\lnlbl{syn:e}
> +//\end{snippet}
>
> #ifdef TEST
> #define RCU_READ_NESTABLE
> diff --git a/CodeSamples/defer/rcu_nest.h b/CodeSamples/defer/rcu_nest.h
> index 7e7b7de..19a0956 100644
> --- a/CodeSamples/defer/rcu_nest.h
> +++ b/CodeSamples/defer/rcu_nest.h
> @@ -42,25 +42,28 @@ static void rcu_init(void)
> init_per_thread(rcu_reader_gp, 0);
> }
>
> -static void rcu_read_lock(void)
> +//\begin{snippet}[labelbase=ln:defer:rcu_nest:read_lock_unlock,gobbleblank=yes,commandchars=\%\@\$]
> +static void rcu_read_lock(void) //\lnlbl{lock:b}
> {
> unsigned long tmp;
> unsigned long *rrgp;
> -
> + //\fcvblank
> /*
> * If this is the outermost RCU read-side critical section,
> * copy the global grace-period counter. In either case,
> * increment the nesting count held in the low-order bits.
> */
>
> - rrgp = &__get_thread_var(rcu_reader_gp);
> + rrgp = &__get_thread_var(rcu_reader_gp); //\lnlbl{lock:readgp}
> +#ifndef FCV_SNIPPET
> retry:
> - tmp = *rrgp;
> - if ((tmp & RCU_GP_CTR_NEST_MASK) == 0)
> - tmp = READ_ONCE(rcu_gp_ctr);
> - tmp++;
> - WRITE_ONCE(*rrgp, tmp);
> - smp_mb();
> +#endif
> + tmp = *rrgp; //\lnlbl{lock:wtmp1}
> + if ((tmp & RCU_GP_CTR_NEST_MASK) == 0) //\lnlbl{lock:checktmp}
> + tmp = READ_ONCE(rcu_gp_ctr); //\lnlbl{lock:wtmp2}
> + tmp++; //\lnlbl{lock:inctmp}
> + WRITE_ONCE(*rrgp, tmp); //\lnlbl{lock:writegp}
> + smp_mb(); //\lnlbl{lock:mb1}
>
> /*
> * A reader could be suspended in between fetching the value of *rrgp
> @@ -71,14 +74,15 @@ retry:
> * ULONG_MAX. To handle this correctly, we adopt the helper function
> * ULONG_CMP_GE.
> */
> -
> +#ifndef FCV_SNIPPET
> if (((tmp & RCU_GP_CTR_NEST_MASK) == 1) &&
> ULONG_CMP_GE(READ_ONCE(rcu_gp_ctr), tmp + MAX_GP_ADV_DISTANCE)) {
> WRITE_ONCE(*rrgp, *rrgp - 1);
> goto retry;
> }
> +#endif
> }
> -
> + //\fcvblank
> static void rcu_read_unlock(void)
> {
> /*
> @@ -86,11 +90,15 @@ static void rcu_read_unlock(void)
> * which had better not initially be zero.
> */
>
> - smp_mb();
> + smp_mb(); //\lnlbl{unlock:mb1}
> +#ifndef FCV_SNIPPET
> #ifdef DEBUG_EXTREME
> BUG_ON((__get_thread_var(rcu_reader_gp) & RCU_GP_CTR_NEST_MASK) != 0);
> -#endif /* #ifdef DEBUG_EXTREME */
> - __get_thread_var(rcu_reader_gp)--;
> +#endif /* #ifdef DEBUG_EXTREME */ //\fcvexclude
> +#endif
> + __get_thread_var(rcu_reader_gp)--; //\lnlbl{unlock:decgp}
> }
> + //\fcvblank
> +//\end{snippet}
>
> extern void synchronize_rcu(void);
> diff --git a/appendix/toyrcu/toyrcu.tex b/appendix/toyrcu/toyrcu.tex
> index ded754d..0e84b05 100644
> --- a/appendix/toyrcu/toyrcu.tex
> +++ b/appendix/toyrcu/toyrcu.tex
> @@ -1395,60 +1395,15 @@ variables.
> \end{listing}
>
> \begin{listing}[tb]
> -{ \scriptsize
> -\begin{verbbox}
> - 1 static void rcu_read_lock(void)
> - 2 {
> - 3 long tmp;
> - 4 long *rrgp;
> - 5
> - 6 rrgp = &__get_thread_var(rcu_reader_gp);
> - 7 tmp = *rrgp;
> - 8 if ((tmp & RCU_GP_CTR_NEST_MASK) == 0)
> - 9 tmp = ACCESS_ONCE(rcu_gp_ctr);
> -10 tmp++;
> -11 *rrgp = tmp;
> -12 smp_mb();
> -13 }
> -14
> -15 static void rcu_read_unlock(void)
> -16 {
> -17 long tmp;
> -18
> -19 smp_mb();
> -20 __get_thread_var(rcu_reader_gp)--;
> -21 }
> -22
> -23 void synchronize_rcu(void)
> -24 {
> -25 int t;
> -26
> -27 smp_mb();
> -28 spin_lock(&rcu_gp_lock);
> -29 ACCESS_ONCE(rcu_gp_ctr) +=
> -30 RCU_GP_CTR_BOTTOM_BIT;
> -31 smp_mb();
> -32 for_each_thread(t) {
> -33 while (rcu_gp_ongoing(t) &&
> -34 ((per_thread(rcu_reader_gp, t) -
> -35 rcu_gp_ctr) < 0)) {
> -36 poll(NULL, 0, 10);
> -37 }
> -38 }
> -39 spin_unlock(&rcu_gp_lock);
> -40 smp_mb();
> -41 }
> -\end{verbbox}
> -}
> -\centering
> -\theverbbox
> +\input{CodeSamples/defer/rcu_nest@read_lock_unlock.fcv}\vspace*{-11pt}\fvset{firstnumber=last}
> +\input{CodeSamples/defer/rcu_nest@synchronize.fcv}\fvset{firstnumber=auto}
> \caption{Nestable RCU Using a Free-Running Counter}
> \label{lst:app:toyrcu:Nestable RCU Using a Free-Running Counter}
> \end{listing}
>
> Listing~\ref{lst:app:toyrcu:Nestable RCU Using a Free-Running Counter}
> (\path{rcu_nest.h} and \path{rcu_nest.c})
> -show an RCU implementation based on a single global free-running counter,
> +shows an RCU implementation based on a single global free-running counter,
> but that permits nesting of RCU read-side critical sections.
> This nestability is accomplished by reserving the low-order bits of the
> global \co{rcu_gp_ctr} to count nesting, using the definitions shown in
> @@ -1472,23 +1427,28 @@ reserves seven bits, for a maximum RCU read-side critical-section
> nesting depth of 127, which should be well in excess of that needed
> by most applications.
>
> +\begin{lineref}[ln:defer:rcu_nest:read_lock_unlock:lock]
> The resulting \co{rcu_read_lock()} implementation is still reasonably
> straightforward.
> -Line~6 places a pointer to this thread's instance of \co{rcu_reader_gp}
> +Line~\lnref{readgp} places a pointer to
> +this thread's instance of \co{rcu_reader_gp}
> into the local variable \co{rrgp}, minimizing the number of expensive
> calls to the pthreads thread-local-state API.
> -Line~7 records the current value of \co{rcu_reader_gp} into another
> -local variable \co{tmp}, and line~8 checks to see if the low-order
> -bits are zero, which would indicate that this is the outermost
> -\co{rcu_read_lock()}.
> -If so, line~9 places the global \co{rcu_gp_ctr} into \co{tmp} because
> -the current value previously fetched by line~7 is likely to be obsolete.
> -In either case, line~10 increments the nesting depth, which you will
> -recall is stored in the seven low-order bits of the counter.
> -Line~11 stores the updated counter back into this thread's instance
> -of \co{rcu_reader_gp}, and, finally, line~12 executes a memory
> -barrier to prevent the RCU read-side critical section from bleeding out
> +Line~\lnref{wtmp1} records the current value of \co{rcu_reader_gp}
> +into another local variable \co{tmp}, and line~\lnref{checktmp} checks
> +to see if the low-order bits are zero, which would indicate that
> +this is the outermost \co{rcu_read_lock()}.
> +If so, line~\lnref{wtmp2} places the global \co{rcu_gp_ctr}
> +into \co{tmp} because the current value previously fetched by
> +line~\lnref{wtmp1} is likely to be obsolete.
> +In either case, line~\lnref{inctmp} increments the nesting depth,
> +which you will recall is stored in the seven low-order bits of the counter.
> +Line~\lnref{writegp} stores the updated counter back into this thread's
> +instance of \co{rcu_reader_gp}, and,
> +finally, line~\lnref{mb1} executes a memory barrier
> +to prevent the RCU read-side critical section from bleeding out
> into the code preceding the call to \co{rcu_read_lock()}.
> +\end{lineref}
>
> In other words, this implementation of \co{rcu_read_lock()} picks up a copy
> of the global \co{rcu_gp_ctr} unless the current invocation of
> @@ -1499,29 +1459,35 @@ Either way, it increments whatever value it fetched in order to record
> an additional nesting level, and stores the result in the current
> thread's instance of \co{rcu_reader_gp}.
>
> +\begin{lineref}[ln:defer:rcu_nest:read_lock_unlock:unlock]
> Interestingly enough, despite their \co{rcu_read_lock()} differences,
> the implementation of \co{rcu_read_unlock()}
> is broadly similar to that shown in
> Section~\ref{sec:app:toyrcu:RCU Based on Free-Running Counter}.
> -Line~19 executes a memory barrier in order to prevent the RCU read-side
> +Line~\lnref{mb1} executes a memory barrier
> +in order to prevent the RCU read-side
> critical section from bleeding out into code following the call
> to \co{rcu_read_unlock()}, and
> -line~20 decrements this thread's instance of \co{rcu_reader_gp},
> +line~\lnref{decgp} decrements this thread's instance of \co{rcu_reader_gp},
> which has the effect of decrementing the nesting count contained in
> \co{rcu_reader_gp}'s low-order bits.
> Debugging versions of this primitive would check (before decrementing!)
> that these low-order bits were non-zero.
> +\end{lineref}
>
> +\begin{lineref}[ln:defer:rcu_nest:synchronize:syn]
> The implementation of \co{synchronize_rcu()} is quite similar to
> that shown in
> Section~\ref{sec:app:toyrcu:RCU Based on Free-Running Counter}.
> There are two differences.
> -The first is that lines~29 and~30 adds \co{RCU_GP_CTR_BOTTOM_BIT}
> -to the global \co{rcu_gp_ctr} instead of adding the constant ``2'',
> -and the second is that the comparison on line~33 has been abstracted
> -out to a separate function, where it checks the bit indicated
> -by \co{RCU_GP_CTR_BOTTOM_BIT} instead of unconditionally checking
> -the low-order bit.
> +The first is that lines~\lnref{incgp1} and~\lnref{incgp2}
> +adds \co{RCU_GP_CTR_BOTTOM_BIT} to the global \co{rcu_gp_ctr}
> +instead of adding the constant ``2'',
> +and the second is that the comparison on line~\lnref{ongoing}
> +has been abstracted out to a separate function,
> +where it checks the bit indicated by \co{RCU_GP_CTR_BOTTOM_BIT}
> +instead of unconditionally checking the low-order bit.
> +\end{lineref}
>
> This approach achieves read-side performance almost equal to that
> shown in
> @@ -1562,10 +1528,12 @@ overhead.
> how could you double the time required to overflow the global
> \co{rcu_gp_ctr}?
> \QuickQuizAnswer{
> + \begin{lineref}[ln:defer:rcu_nest:synchronize:syn]
> One way would be to replace the magnitude comparison on
> - lines~33 and 34 with an inequality check of the per-thread
> - \co{rcu_reader_gp} variable against
> + lines~\lnref{lt1} and \lnref{lt2} with an inequality check of
> + the per-thread \co{rcu_reader_gp} variable against
> \co{rcu_gp_ctr+RCU_GP_CTR_BOTTOM_BIT}.
> + \end{lineref}
> } \QuickQuizEnd
>
> \QuickQuiz{}
> --
> 2.7.4
>
prev parent reply other threads:[~2019-05-29 13:30 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-05-29 4:47 [PATCH] rcu_nest: Update description of rcu_nest.[hc] Junchang Wang
2019-05-29 13:30 ` Paul E. McKenney [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190529133041.GY28207@linux.ibm.com \
--to=paulmck@linux.ibm.com \
--cc=akiyks@gmail.com \
--cc=junchangwang@gmail.com \
--cc=perfbook@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.