2019-06-01 22:32:38

by Joel Fernandes

[permalink] [raw]
Subject: [RFC 0/6] Harden list_for_each_entry_rcu() and family

Hi,
Please consider this as an RFC / proof-of-concept to gather some feedback. This
series aims to provide lockdep checking to RCU list macros.

RCU has a number of primitives for "consumption" of an RCU protected pointer.
Most of the time, these consumers make sure that such accesses are under a RCU
reader-section (such as rcu_dereference{,sched,bh} or under a lock, such as
with rcu_dereference_protected()).

However, there are other ways to consume RCU pointers, such as by
list_for_each_enry_rcu or hlist_for_each_enry_rcu. Unlike the rcu_dereference
family, these consumers do no lockdep checking at all. And with the growing
number of RCU list uses, it is possible for bugs to creep in and go unnoticed
which lockdep checks can catch.

Since RCU consolidation efforts last year, the different traditional RCU
flavors (preempt, bh, sched) are all consolidated. In other words, any of these
flavors can cause a reader section to occur and all of them must cease before
the reader section is considered to be unlocked.

Now, the list_for_each_entry_rcu and family are different from the
rcu_dereference family in that, there is no _bh or _sched version of this
macro. They are used under many different RCU reader flavors, and also SRCU.
This series adds a new internal function rcu_read_lock_any_held() which checks
if any reader section is active at all, when these macros are called. If no
reader section exists, then the optional fourth argument to
list_for_each_entry_rcu() can be a lockdep expression which is evaluated
(similar to how rcu_dereference_check() works).

The optional argument trick to list_for_each_entry_rcu() can also be used in
the future to possibly remove rcu_dereference_{,bh,sched}_protected() API and
we can pass an optional lockdep expression to rcu_dereference() itself. Thus
eliminating 3 more RCU APIs.

Note that some list macro wrappers already do their own lockdep checking in the
caller side. These can be eliminated in favor of the built-in lockdep checking
in the list macro that this series adds. For example, workqueue code has a
assert_rcu_or_wq_mutex() function which is called in for_each_wq(). This
series replaces that in favor of the built-in one.

Also in the future, we can extend these checks to list_entry_rcu() and other
list macros as well.

Joel Fernandes (Google) (6):
rcu: Add support for consolidated-RCU reader checking
ipv4: add lockdep condition to fix for_each_entry
driver/core: Convert to use built-in RCU list checking
workqueue: Convert for_each_wq to use built-in list check
x86/pci: Pass lockdep condition to pcm_mmcfg_list iterator
acpi: Use built-in RCU list checking for acpi_ioremaps list

arch/x86/pci/mmconfig-shared.c | 5 +++--
drivers/acpi/osl.c | 6 +++--
drivers/base/base.h | 1 +
drivers/base/core.c | 10 +++++++++
drivers/base/power/runtime.c | 15 ++++++++-----
include/linux/rculist.h | 40 ++++++++++++++++++++++++++++++----
include/linux/rcupdate.h | 7 ++++++
kernel/rcu/update.c | 26 ++++++++++++++++++++++
kernel/workqueue.c | 5 ++---
net/ipv4/fib_frontend.c | 3 ++-
10 files changed, 101 insertions(+), 17 deletions(-)

--
2.22.0.rc1.311.g5d7573a151-goog


2019-06-01 22:32:39

by Joel Fernandes

[permalink] [raw]
Subject: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

This patch adds support for checking RCU reader sections in list
traversal macros. Optionally, if the list macro is called under SRCU or
other lock/mutex protection, then appropriate lockdep expressions can be
passed to make the checks pass.

Existing list_for_each_entry_rcu() invocations don't need to pass the
optional fourth argument (cond) unless they are under some non-RCU
protection and needs to make lockdep check pass.

Signed-off-by: Joel Fernandes (Google) <[email protected]>
---
include/linux/rculist.h | 40 ++++++++++++++++++++++++++++++++++++----
include/linux/rcupdate.h | 7 +++++++
kernel/rcu/update.c | 26 ++++++++++++++++++++++++++
3 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/include/linux/rculist.h b/include/linux/rculist.h
index e91ec9ddcd30..b641fdd9f1a2 100644
--- a/include/linux/rculist.h
+++ b/include/linux/rculist.h
@@ -40,6 +40,25 @@ static inline void INIT_LIST_HEAD_RCU(struct list_head *list)
*/
#define list_next_rcu(list) (*((struct list_head __rcu **)(&(list)->next)))

+/*
+ * Check during list traversal that we are within an RCU reader
+ */
+#define __list_check_rcu() \
+ RCU_LOCKDEP_WARN(!rcu_read_lock_any_held(), \
+ "RCU-list traversed in non-reader section!")
+
+static inline void __list_check_rcu_cond(int dummy, ...)
+{
+ va_list ap;
+ int cond;
+
+ va_start(ap, dummy);
+ cond = va_arg(ap, int);
+ va_end(ap);
+
+ RCU_LOCKDEP_WARN(!cond && !rcu_read_lock_any_held(),
+ "RCU-list traversed in non-reader section!");
+}
/*
* Insert a new entry between two known consecutive entries.
*
@@ -338,6 +357,9 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
member) : NULL; \
})

+#define SIXTH_ARG(a1, a2, a3, a4, a5, a6, ...) a6
+#define COUNT_VARGS(...) SIXTH_ARG(dummy, ## __VA_ARGS__, 4, 3, 2, 1, 0)
+
/**
* list_for_each_entry_rcu - iterate over rcu list of given type
* @pos: the type * to use as a loop cursor.
@@ -348,9 +370,14 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
* the _rcu list-mutation primitives such as list_add_rcu()
* as long as the traversal is guarded by rcu_read_lock().
*/
-#define list_for_each_entry_rcu(pos, head, member) \
- for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
- &pos->member != (head); \
+#define list_for_each_entry_rcu(pos, head, member, cond...) \
+ if (COUNT_VARGS(cond) != 0) { \
+ __list_check_rcu_cond(0, ## cond); \
+ } else { \
+ __list_check_rcu(); \
+ } \
+ for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
+ &pos->member != (head); \
pos = list_entry_rcu(pos->member.next, typeof(*pos), member))

/**
@@ -621,7 +648,12 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
* the _rcu list-mutation primitives such as hlist_add_head_rcu()
* as long as the traversal is guarded by rcu_read_lock().
*/
-#define hlist_for_each_entry_rcu(pos, head, member) \
+#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
+ if (COUNT_VARGS(cond) != 0) { \
+ __list_check_rcu_cond(0, ## cond); \
+ } else { \
+ __list_check_rcu(); \
+ } \
for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
typeof(*(pos)), member); \
pos; \
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 922bb6848813..712b464ab960 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -223,6 +223,7 @@ int debug_lockdep_rcu_enabled(void);
int rcu_read_lock_held(void);
int rcu_read_lock_bh_held(void);
int rcu_read_lock_sched_held(void);
+int rcu_read_lock_any_held(void);

#else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */

@@ -243,6 +244,12 @@ static inline int rcu_read_lock_sched_held(void)
{
return !preemptible();
}
+
+static inline int rcu_read_lock_any_held(void)
+{
+ return !preemptible();
+}
+
#endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */

#ifdef CONFIG_PROVE_RCU
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c
index c3bf44ba42e5..9cb30006a5e1 100644
--- a/kernel/rcu/update.c
+++ b/kernel/rcu/update.c
@@ -298,6 +298,32 @@ int rcu_read_lock_bh_held(void)
}
EXPORT_SYMBOL_GPL(rcu_read_lock_bh_held);

+int rcu_read_lock_any_held(void)
+{
+ int lockdep_opinion = 0;
+
+ if (!debug_lockdep_rcu_enabled())
+ return 1;
+ if (!rcu_is_watching())
+ return 0;
+ if (!rcu_lockdep_current_cpu_online())
+ return 0;
+
+ /* Preemptible RCU flavor */
+ if (lock_is_held(&rcu_lock_map))
+ return 1;
+
+ /* BH flavor */
+ if (in_softirq() || irqs_disabled())
+ return 1;
+
+ /* Sched flavor */
+ if (debug_locks)
+ lockdep_opinion = lock_is_held(&rcu_sched_lock_map);
+ return lockdep_opinion || !preemptible();
+}
+EXPORT_SYMBOL_GPL(rcu_read_lock_any_held);
+
#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */

/**
--
2.22.0.rc1.311.g5d7573a151-goog

2019-06-03 10:05:21

by Peter Zijlstra

[permalink] [raw]
Subject: Re: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

On Sat, Jun 01, 2019 at 06:27:33PM -0400, Joel Fernandes (Google) wrote:
> +#define list_for_each_entry_rcu(pos, head, member, cond...) \
> + if (COUNT_VARGS(cond) != 0) { \
> + __list_check_rcu_cond(0, ## cond); \
> + } else { \
> + __list_check_rcu(); \
> + } \
> + for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> + &pos->member != (head); \
> pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
>
> /**
> @@ -621,7 +648,12 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
> * the _rcu list-mutation primitives such as hlist_add_head_rcu()
> * as long as the traversal is guarded by rcu_read_lock().
> */
> +#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
> + if (COUNT_VARGS(cond) != 0) { \
> + __list_check_rcu_cond(0, ## cond); \
> + } else { \
> + __list_check_rcu(); \
> + } \
> for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
> typeof(*(pos)), member); \
> pos; \


This breaks code like:

if (...)
list_for_each_entry_rcu(...);

as they are no longer a single statement. You'll have to frob it into
the initializer part of the for statement.

2019-06-03 17:05:51

by Joel Fernandes

[permalink] [raw]
Subject: Re: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

On Mon, Jun 03, 2019 at 10:01:28AM +0200, Peter Zijlstra wrote:
> On Sat, Jun 01, 2019 at 06:27:33PM -0400, Joel Fernandes (Google) wrote:
> > +#define list_for_each_entry_rcu(pos, head, member, cond...) \
> > + if (COUNT_VARGS(cond) != 0) { \
> > + __list_check_rcu_cond(0, ## cond); \
> > + } else { \
> > + __list_check_rcu(); \
> > + } \
> > + for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> > + &pos->member != (head); \
> > pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
> >
> > /**
> > @@ -621,7 +648,12 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
> > * the _rcu list-mutation primitives such as hlist_add_head_rcu()
> > * as long as the traversal is guarded by rcu_read_lock().
> > */
> > +#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
> > + if (COUNT_VARGS(cond) != 0) { \
> > + __list_check_rcu_cond(0, ## cond); \
> > + } else { \
> > + __list_check_rcu(); \
> > + } \
> > for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
> > typeof(*(pos)), member); \
> > pos; \
>
>
> This breaks code like:
>
> if (...)
> list_for_each_entry_rcu(...);
>
> as they are no longer a single statement. You'll have to frob it into
> the initializer part of the for statement.

Thanks a lot for that. I fixed it as below (diff is on top of the patch):

If not for that '##' , I could have abstracted the whole if/else
expression into its own macro and called it from list_for_each_entry_rcu() to
keep it more clean.

---8<-----------------------

diff --git a/include/linux/rculist.h b/include/linux/rculist.h
index b641fdd9f1a2..cc742d294bb0 100644
--- a/include/linux/rculist.h
+++ b/include/linux/rculist.h
@@ -371,12 +372,15 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
* as long as the traversal is guarded by rcu_read_lock().
*/
#define list_for_each_entry_rcu(pos, head, member, cond...) \
- if (COUNT_VARGS(cond) != 0) { \
- __list_check_rcu_cond(0, ## cond); \
- } else { \
- __list_check_rcu(); \
- } \
- for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
+ for ( \
+ ({ \
+ if (COUNT_VARGS(cond) != 0) { \
+ __list_check_rcu_cond(0, ## cond); \
+ } else { \
+ __list_check_rcu_nocond(); \
+ } \
+ }), \
+ pos = list_entry_rcu((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry_rcu(pos->member.next, typeof(*pos), member))

@@ -649,12 +653,15 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
* as long as the traversal is guarded by rcu_read_lock().
*/
#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
- if (COUNT_VARGS(cond) != 0) { \
- __list_check_rcu_cond(0, ## cond); \
- } else { \
- __list_check_rcu(); \
- } \
- for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
+ for ( \
+ ({ \
+ if (COUNT_VARGS(cond) != 0) { \
+ __list_check_rcu_cond(0, ## cond); \
+ } else { \
+ __list_check_rcu_nocond(); \
+ } \
+ }), \
+ pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
typeof(*(pos)), member); \
pos; \
pos = hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu(\
--
2.22.0.rc1.311.g5d7573a151-goog

2019-06-03 19:44:38

by Joel Fernandes

[permalink] [raw]
Subject: Re: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

On Mon, Jun 03, 2019 at 10:18:47AM -0400, Joel Fernandes wrote:
> On Mon, Jun 03, 2019 at 10:01:28AM +0200, Peter Zijlstra wrote:
> > On Sat, Jun 01, 2019 at 06:27:33PM -0400, Joel Fernandes (Google) wrote:
> > > +#define list_for_each_entry_rcu(pos, head, member, cond...) \
> > > + if (COUNT_VARGS(cond) != 0) { \
> > > + __list_check_rcu_cond(0, ## cond); \
> > > + } else { \
> > > + __list_check_rcu(); \
> > > + } \
> > > + for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> > > + &pos->member != (head); \
> > > pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
> > >
> > > /**
> > > @@ -621,7 +648,12 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
> > > * the _rcu list-mutation primitives such as hlist_add_head_rcu()
> > > * as long as the traversal is guarded by rcu_read_lock().
> > > */
> > > +#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
> > > + if (COUNT_VARGS(cond) != 0) { \
> > > + __list_check_rcu_cond(0, ## cond); \
> > > + } else { \
> > > + __list_check_rcu(); \
> > > + } \
> > > for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
> > > typeof(*(pos)), member); \
> > > pos; \
> >
> >
> > This breaks code like:
> >
> > if (...)
> > list_for_each_entry_rcu(...);
> >
> > as they are no longer a single statement. You'll have to frob it into
> > the initializer part of the for statement.
>
> Thanks a lot for that. I fixed it as below (diff is on top of the patch):
>
> If not for that '##' , I could have abstracted the whole if/else
> expression into its own macro and called it from list_for_each_entry_rcu() to
> keep it more clean.

Actually was able to roll the if/else into its own macro as well, thus
keeping it clean. thanks!

---8<-----------------------

diff --git a/include/linux/rculist.h b/include/linux/rculist.h
index b641fdd9f1a2..cc9c382b080c 100644
--- a/include/linux/rculist.h
+++ b/include/linux/rculist.h
@@ -43,7 +43,11 @@ static inline void INIT_LIST_HEAD_RCU(struct list_head *list)
/*
* Check during list traversal that we are within an RCU reader
*/
-#define __list_check_rcu() \
+
+#define SIXTH_ARG(a1, a2, a3, a4, a5, a6, ...) a6
+#define COUNT_VARGS(...) SIXTH_ARG(dummy, ## __VA_ARGS__, 4, 3, 2, 1, 0)
+
+#define __list_check_rcu_nocond() \
RCU_LOCKDEP_WARN(!rcu_read_lock_any_held(), \
"RCU-list traversed in non-reader section!")

@@ -59,6 +63,16 @@ static inline void __list_check_rcu_cond(int dummy, ...)
RCU_LOCKDEP_WARN(!cond && !rcu_read_lock_any_held(),
"RCU-list traversed in non-reader section!");
}
+
+#define __list_check_rcu(cond...) \
+ ({ \
+ if (COUNT_VARGS(cond) != 0) { \
+ __list_check_rcu_cond(0, ## cond); \
+ } else { \
+ __list_check_rcu_nocond(); \
+ } \
+ })
+
/*
* Insert a new entry between two known consecutive entries.
*
@@ -357,9 +371,6 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
member) : NULL; \
})

-#define SIXTH_ARG(a1, a2, a3, a4, a5, a6, ...) a6
-#define COUNT_VARGS(...) SIXTH_ARG(dummy, ## __VA_ARGS__, 4, 3, 2, 1, 0)
-
/**
* list_for_each_entry_rcu - iterate over rcu list of given type
* @pos: the type * to use as a loop cursor.
@@ -371,12 +382,8 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
* as long as the traversal is guarded by rcu_read_lock().
*/
#define list_for_each_entry_rcu(pos, head, member, cond...) \
- if (COUNT_VARGS(cond) != 0) { \
- __list_check_rcu_cond(0, ## cond); \
- } else { \
- __list_check_rcu(); \
- } \
- for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
+ for (__list_check_rcu(cond), \
+ pos = list_entry_rcu((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry_rcu(pos->member.next, typeof(*pos), member))

@@ -649,12 +656,8 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
* as long as the traversal is guarded by rcu_read_lock().
*/
#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
- if (COUNT_VARGS(cond) != 0) { \
- __list_check_rcu_cond(0, ## cond); \
- } else { \
- __list_check_rcu(); \
- } \
- for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
+ for (__list_check_rcu(cond), \
+ pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
typeof(*(pos)), member); \
pos; \
pos = hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu(\
--
2.22.0.rc1.311.g5d7573a151-goog

2019-06-04 10:55:39

by Steven Rostedt

[permalink] [raw]
Subject: Re: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

On Mon, 3 Jun 2019 10:18:47 -0400
Joel Fernandes <[email protected]> wrote:

> On Mon, Jun 03, 2019 at 10:01:28AM +0200, Peter Zijlstra wrote:
> > On Sat, Jun 01, 2019 at 06:27:33PM -0400, Joel Fernandes (Google) wrote:
> > > +#define list_for_each_entry_rcu(pos, head, member, cond...) \
> > > + if (COUNT_VARGS(cond) != 0) { \
> > > + __list_check_rcu_cond(0, ## cond); \
> > > + } else { \
> > > + __list_check_rcu(); \
> > > + } \
> > > + for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> > > + &pos->member != (head); \
> > > pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
> > >
> > > /**
> > > @@ -621,7 +648,12 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
> > > * the _rcu list-mutation primitives such as hlist_add_head_rcu()
> > > * as long as the traversal is guarded by rcu_read_lock().
> > > */
> > > +#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
> > > + if (COUNT_VARGS(cond) != 0) { \
> > > + __list_check_rcu_cond(0, ## cond); \
> > > + } else { \
> > > + __list_check_rcu(); \
> > > + } \
> > > for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
> > > typeof(*(pos)), member); \
> > > pos; \
> >
> >
> > This breaks code like:
> >
> > if (...)
> > list_for_each_entry_rcu(...);
> >
> > as they are no longer a single statement. You'll have to frob it into
> > the initializer part of the for statement.
>
> Thanks a lot for that. I fixed it as below (diff is on top of the patch):
>
> If not for that '##' , I could have abstracted the whole if/else
> expression into its own macro and called it from list_for_each_entry_rcu() to
> keep it more clean.
>
> ---8<-----------------------
>
> diff --git a/include/linux/rculist.h b/include/linux/rculist.h
> index b641fdd9f1a2..cc742d294bb0 100644
> --- a/include/linux/rculist.h
> +++ b/include/linux/rculist.h
> @@ -371,12 +372,15 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
> * as long as the traversal is guarded by rcu_read_lock().
> */
> #define list_for_each_entry_rcu(pos, head, member, cond...) \
> - if (COUNT_VARGS(cond) != 0) { \
> - __list_check_rcu_cond(0, ## cond); \
> - } else { \
> - __list_check_rcu(); \
> - } \
> - for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> + for ( \
> + ({ \
> + if (COUNT_VARGS(cond) != 0) { \
> + __list_check_rcu_cond(0, ## cond); \
> + } else { \
> + __list_check_rcu_nocond(); \
> + } \
> + }), \

For easier to read I would do something like this:

#define check_rcu_list(cond) \
({ \
if (COUNT_VARGS(cond) != 0) \
__list_check_rcu_cond(0, ## cond); \
else \
__list_check_rcu_nocond(); \
})

#define list_for_each_entry_rcu(pos, head, member, cond...) \
for (check_rcu_list(cond), \


-- Steve

> + pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> &pos->member != (head); \
> pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
>
> @@ -649,12 +653,15 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
> * as long as the traversal is guarded by rcu_read_lock().
> */
> #define hlist_for_each_entry_rcu(pos, head, member, cond...) \
> - if (COUNT_VARGS(cond) != 0) { \
> - __list_check_rcu_cond(0, ## cond); \
> - } else { \
> - __list_check_rcu(); \
> - } \
> - for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
> + for ( \
> + ({ \
> + if (COUNT_VARGS(cond) != 0) { \
> + __list_check_rcu_cond(0, ## cond); \
> + } else { \
> + __list_check_rcu_nocond(); \
> + } \
> + }), \
> + pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
> typeof(*(pos)), member); \
> pos; \
> pos = hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu(\

2019-06-04 14:03:51

by Rasmus Villemoes

[permalink] [raw]
Subject: Re: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

On 02/06/2019 00.27, Joel Fernandes (Google) wrote:
> This patch adds support for checking RCU reader sections in list
> traversal macros. Optionally, if the list macro is called under SRCU or
> other lock/mutex protection, then appropriate lockdep expressions can be
> passed to make the checks pass.
>
> Existing list_for_each_entry_rcu() invocations don't need to pass the
> optional fourth argument (cond) unless they are under some non-RCU
> protection and needs to make lockdep check pass.
>
> Signed-off-by: Joel Fernandes (Google) <[email protected]>
> ---
> include/linux/rculist.h | 40 ++++++++++++++++++++++++++++++++++++----
> include/linux/rcupdate.h | 7 +++++++
> kernel/rcu/update.c | 26 ++++++++++++++++++++++++++
> 3 files changed, 69 insertions(+), 4 deletions(-)
>
> diff --git a/include/linux/rculist.h b/include/linux/rculist.h
> index e91ec9ddcd30..b641fdd9f1a2 100644
> --- a/include/linux/rculist.h
> +++ b/include/linux/rculist.h
> @@ -40,6 +40,25 @@ static inline void INIT_LIST_HEAD_RCU(struct list_head *list)
> */
> #define list_next_rcu(list) (*((struct list_head __rcu **)(&(list)->next)))
>
> +/*
> + * Check during list traversal that we are within an RCU reader
> + */
> +#define __list_check_rcu() \
> + RCU_LOCKDEP_WARN(!rcu_read_lock_any_held(), \
> + "RCU-list traversed in non-reader section!")
> +
> +static inline void __list_check_rcu_cond(int dummy, ...)
> +{
> + va_list ap;
> + int cond;
> +
> + va_start(ap, dummy);
> + cond = va_arg(ap, int);
> + va_end(ap);
> +
> + RCU_LOCKDEP_WARN(!cond && !rcu_read_lock_any_held(),
> + "RCU-list traversed in non-reader section!");
> +}
> /*
> * Insert a new entry between two known consecutive entries.
> *
> @@ -338,6 +357,9 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
> member) : NULL; \
> })
>
> +#define SIXTH_ARG(a1, a2, a3, a4, a5, a6, ...) a6
> +#define COUNT_VARGS(...) SIXTH_ARG(dummy, ## __VA_ARGS__, 4, 3, 2, 1, 0)
> +> /**
> * list_for_each_entry_rcu - iterate over rcu list of given type
> * @pos: the type * to use as a loop cursor.
> @@ -348,9 +370,14 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
> * the _rcu list-mutation primitives such as list_add_rcu()
> * as long as the traversal is guarded by rcu_read_lock().
> */
> -#define list_for_each_entry_rcu(pos, head, member) \
> - for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> - &pos->member != (head); \
> +#define list_for_each_entry_rcu(pos, head, member, cond...) \
> + if (COUNT_VARGS(cond) != 0) { \
> + __list_check_rcu_cond(0, ## cond); \
> + } else { \
> + __list_check_rcu(); \
> + } \
> + for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> + &pos->member != (head); \
> pos = list_entry_rcu(pos->member.next, typeof(*pos), member))

Wouldn't something as simple as

#define __list_check_rcu(dummy, cond, ...) \
RCU_LOCKDEP_WARN(!cond && !rcu_read_lock_any_held(), \
"RCU-list traversed in non-reader section!");

for ( ({ __list_check_rcu(junk, ##cond, 0); }), pos = ... )

work just as well (i.e., no need for two list_check_rcu and
list_check_rcu_cond variants)? If there's an optional cond, we use that,
if not, we pick the trailing 0, so !cond disappears and it reduces to
your __list_check_rcu(). Moreover, this ensures the RCU_LOCKDEP_WARN
expansion actually picks up the __LINE__ and __FILE__ where the for loop
is used, and not the __FILE__ and __LINE__ of the static inline function
from the header file. It also makes it a bit more type safe/type generic
(if the cond expression happened to have type long or u64 something
rather odd could happen with the inline vararg function).

Rasmus

2019-06-04 17:49:27

by Joel Fernandes

[permalink] [raw]
Subject: Re: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

On Tue, Jun 04, 2019 at 06:53:58AM -0400, Steven Rostedt wrote:
> On Mon, 3 Jun 2019 10:18:47 -0400
> Joel Fernandes <[email protected]> wrote:
>
> > On Mon, Jun 03, 2019 at 10:01:28AM +0200, Peter Zijlstra wrote:
> > > On Sat, Jun 01, 2019 at 06:27:33PM -0400, Joel Fernandes (Google) wrote:
> > > > +#define list_for_each_entry_rcu(pos, head, member, cond...) \
> > > > + if (COUNT_VARGS(cond) != 0) { \
> > > > + __list_check_rcu_cond(0, ## cond); \
> > > > + } else { \
> > > > + __list_check_rcu(); \
> > > > + } \
> > > > + for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> > > > + &pos->member != (head); \
> > > > pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
> > > >
> > > > /**
> > > > @@ -621,7 +648,12 @@ static inline void hlist_add_behind_rcu(struct hlist_node *n,
> > > > * the _rcu list-mutation primitives such as hlist_add_head_rcu()
> > > > * as long as the traversal is guarded by rcu_read_lock().
> > > > */
> > > > +#define hlist_for_each_entry_rcu(pos, head, member, cond...) \
> > > > + if (COUNT_VARGS(cond) != 0) { \
> > > > + __list_check_rcu_cond(0, ## cond); \
> > > > + } else { \
> > > > + __list_check_rcu(); \
> > > > + } \
> > > > for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
> > > > typeof(*(pos)), member); \
> > > > pos; \
> > >
> > >
> > > This breaks code like:
> > >
> > > if (...)
> > > list_for_each_entry_rcu(...);
> > >
> > > as they are no longer a single statement. You'll have to frob it into
> > > the initializer part of the for statement.
> >
> > Thanks a lot for that. I fixed it as below (diff is on top of the patch):
> >
> > If not for that '##' , I could have abstracted the whole if/else
> > expression into its own macro and called it from list_for_each_entry_rcu() to
> > keep it more clean.
> >
> > ---8<-----------------------
> >
> > diff --git a/include/linux/rculist.h b/include/linux/rculist.h
> > index b641fdd9f1a2..cc742d294bb0 100644
> > --- a/include/linux/rculist.h
> > +++ b/include/linux/rculist.h
> > @@ -371,12 +372,15 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
> > * as long as the traversal is guarded by rcu_read_lock().
> > */
> > #define list_for_each_entry_rcu(pos, head, member, cond...) \
> > - if (COUNT_VARGS(cond) != 0) { \
> > - __list_check_rcu_cond(0, ## cond); \
> > - } else { \
> > - __list_check_rcu(); \
> > - } \
> > - for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> > + for ( \
> > + ({ \
> > + if (COUNT_VARGS(cond) != 0) { \
> > + __list_check_rcu_cond(0, ## cond); \
> > + } else { \
> > + __list_check_rcu_nocond(); \
> > + } \
> > + }), \
>
> For easier to read I would do something like this:
>
> #define check_rcu_list(cond) \
> ({ \
> if (COUNT_VARGS(cond) != 0) \
> __list_check_rcu_cond(0, ## cond); \
> else \
> __list_check_rcu_nocond(); \
> })
>
> #define list_for_each_entry_rcu(pos, head, member, cond...) \
> for (check_rcu_list(cond), \

Yes, already doing it this way as I replied to Peter here:
https://lore.kernel.org/patchwork/patch/1082846/#1278489

Thanks!

- Joel


2019-06-04 23:59:02

by Joel Fernandes

[permalink] [raw]
Subject: Re: [RFC 1/6] rcu: Add support for consolidated-RCU reader checking

On Tue, Jun 04, 2019 at 04:01:00PM +0200, Rasmus Villemoes wrote:
> On 02/06/2019 00.27, Joel Fernandes (Google) wrote:
> > This patch adds support for checking RCU reader sections in list
> > traversal macros. Optionally, if the list macro is called under SRCU or
> > other lock/mutex protection, then appropriate lockdep expressions can be
> > passed to make the checks pass.
> >
> > Existing list_for_each_entry_rcu() invocations don't need to pass the
> > optional fourth argument (cond) unless they are under some non-RCU
> > protection and needs to make lockdep check pass.
> >
> > Signed-off-by: Joel Fernandes (Google) <[email protected]>
> > ---
> > include/linux/rculist.h | 40 ++++++++++++++++++++++++++++++++++++----
> > include/linux/rcupdate.h | 7 +++++++
> > kernel/rcu/update.c | 26 ++++++++++++++++++++++++++
> > 3 files changed, 69 insertions(+), 4 deletions(-)
> >
> > diff --git a/include/linux/rculist.h b/include/linux/rculist.h
> > index e91ec9ddcd30..b641fdd9f1a2 100644
> > --- a/include/linux/rculist.h
> > +++ b/include/linux/rculist.h
> > @@ -40,6 +40,25 @@ static inline void INIT_LIST_HEAD_RCU(struct list_head *list)
> > */
> > #define list_next_rcu(list) (*((struct list_head __rcu **)(&(list)->next)))
> >
> > +/*
> > + * Check during list traversal that we are within an RCU reader
> > + */
> > +#define __list_check_rcu() \
> > + RCU_LOCKDEP_WARN(!rcu_read_lock_any_held(), \
> > + "RCU-list traversed in non-reader section!")
> > +
> > +static inline void __list_check_rcu_cond(int dummy, ...)
> > +{
> > + va_list ap;
> > + int cond;
> > +
> > + va_start(ap, dummy);
> > + cond = va_arg(ap, int);
> > + va_end(ap);
> > +
> > + RCU_LOCKDEP_WARN(!cond && !rcu_read_lock_any_held(),
> > + "RCU-list traversed in non-reader section!");
> > +}
> > /*
> > * Insert a new entry between two known consecutive entries.
> > *
> > @@ -338,6 +357,9 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
> > member) : NULL; \
> > })
> >
> > +#define SIXTH_ARG(a1, a2, a3, a4, a5, a6, ...) a6
> > +#define COUNT_VARGS(...) SIXTH_ARG(dummy, ## __VA_ARGS__, 4, 3, 2, 1, 0)
> > +> /**
> > * list_for_each_entry_rcu - iterate over rcu list of given type
> > * @pos: the type * to use as a loop cursor.
> > @@ -348,9 +370,14 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,
> > * the _rcu list-mutation primitives such as list_add_rcu()
> > * as long as the traversal is guarded by rcu_read_lock().
> > */
> > -#define list_for_each_entry_rcu(pos, head, member) \
> > - for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> > - &pos->member != (head); \
> > +#define list_for_each_entry_rcu(pos, head, member, cond...) \
> > + if (COUNT_VARGS(cond) != 0) { \
> > + __list_check_rcu_cond(0, ## cond); \
> > + } else { \
> > + __list_check_rcu(); \
> > + } \
> > + for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \
> > + &pos->member != (head); \
> > pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
>
> Wouldn't something as simple as
>
> #define __list_check_rcu(dummy, cond, ...) \
> RCU_LOCKDEP_WARN(!cond && !rcu_read_lock_any_held(), \
> "RCU-list traversed in non-reader section!");
>
> for ( ({ __list_check_rcu(junk, ##cond, 0); }), pos = ... )
>
> work just as well (i.e., no need for two list_check_rcu and
> list_check_rcu_cond variants)? If there's an optional cond, we use that,
> if not, we pick the trailing 0, so !cond disappears and it reduces to
> your __list_check_rcu(). Moreover, this ensures the RCU_LOCKDEP_WARN
> expansion actually picks up the __LINE__ and __FILE__ where the for loop
> is used, and not the __FILE__ and __LINE__ of the static inline function
> from the header file. It also makes it a bit more type safe/type generic
> (if the cond expression happened to have type long or u64 something
> rather odd could happen with the inline vararg function).

This is much better. I will do it this way. Thank you!

- Joel