rculist: Use WRITE_ONCE() when deleting from reader-visible list

The various RCU list-deletion macros (list_del_rcu(),
hlist_del_init_rcu(), hlist_del_rcu(), hlist_bl_del_init_rcu(),
hlist_bl_del_rcu(), hlist_nulls_del_init_rcu(), and hlist_nulls_del_rcu())
do plain stores into the ->next pointer of the preceding list elemment.
Unfortunately, the compiler is within its rights to (for example) use
byte-at-a-time writes to update the pointer, which would fatally confuse
concurrent readers.  This patch therefore adds the needed WRITE_ONCE()
macros.

KernelThreadSanitizer (KTSAN) reported the __hlist_del() issue, which
is a problem when __hlist_del() is invoked by hlist_del_rcu().

Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Reviewed-by: Josh Triplett <josh@joshtriplett.org>
This commit is contained in:
Paul E. McKenney 2015-09-18 08:45:22 -07:00
parent e62e3f620b
commit 7f5f873c6a
3 changed files with 8 additions and 5 deletions

View File

@ -87,7 +87,7 @@ static inline void list_add_tail(struct list_head *new, struct list_head *head)
static inline void __list_del(struct list_head * prev, struct list_head * next) static inline void __list_del(struct list_head * prev, struct list_head * next)
{ {
next->prev = prev; next->prev = prev;
prev->next = next; WRITE_ONCE(prev->next, next);
} }
/** /**
@ -615,7 +615,8 @@ static inline void __hlist_del(struct hlist_node *n)
{ {
struct hlist_node *next = n->next; struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev; struct hlist_node **pprev = n->pprev;
*pprev = next;
WRITE_ONCE(*pprev, next);
if (next) if (next)
next->pprev = pprev; next->pprev = pprev;
} }

View File

@ -93,9 +93,10 @@ static inline void __hlist_bl_del(struct hlist_bl_node *n)
LIST_BL_BUG_ON((unsigned long)n & LIST_BL_LOCKMASK); LIST_BL_BUG_ON((unsigned long)n & LIST_BL_LOCKMASK);
/* pprev may be `first`, so be careful not to lose the lock bit */ /* pprev may be `first`, so be careful not to lose the lock bit */
*pprev = (struct hlist_bl_node *) WRITE_ONCE(*pprev,
(struct hlist_bl_node *)
((unsigned long)next | ((unsigned long)next |
((unsigned long)*pprev & LIST_BL_LOCKMASK)); ((unsigned long)*pprev & LIST_BL_LOCKMASK)));
if (next) if (next)
next->pprev = pprev; next->pprev = pprev;
} }

View File

@ -76,7 +76,8 @@ static inline void __hlist_nulls_del(struct hlist_nulls_node *n)
{ {
struct hlist_nulls_node *next = n->next; struct hlist_nulls_node *next = n->next;
struct hlist_nulls_node **pprev = n->pprev; struct hlist_nulls_node **pprev = n->pprev;
*pprev = next;
WRITE_ONCE(*pprev, next);
if (!is_a_nulls(next)) if (!is_a_nulls(next))
next->pprev = pprev; next->pprev = pprev;
} }