Re: [RFC PATCH 3/4] hp: Implement Hazard Pointers
On 2024-10-07 15:47, Boqun Feng wrote: On Thu, Oct 03, 2024 at 09:30:53AM -0400, Mathieu Desnoyers wrote: [...] + /* +* Use RCU dereference without lockdep checks, because +* lockdep is not aware of HP guarantees. +*/ + addr2 = rcu_access_pointer(*addr_p);/* Load A */ Why rcu_access_pointer() instead of READ_ONCE()? Because you want to mark the head of address dependency? Yes, the intent here is to mark the address dependency and provide a publication guarantee similar to RCU pairing rcu_assign_pointer and rcu_dereference. Do you see any reason why READ_ONCE() would suffice here ? READ_ONCE() also provides address dependencies. See the "DEPENDENCY RELATIONS: data, addr, and ctrl" section in tools/memory-model/Documentation/explanantion.txt. Fair point, so let's use READ_ONCE() then. Thanks, Mathieu Regards, Boqun Thanks, Mathieu -- Mathieu Desnoyers EfficiOS Inc. https://www.efficios.com
Re: [RFC PATCH 3/4] hp: Implement Hazard Pointers
On Thu, Oct 03, 2024 at 09:30:53AM -0400, Mathieu Desnoyers wrote: [...] > > > + /* > > > + * Use RCU dereference without lockdep checks, because > > > + * lockdep is not aware of HP guarantees. > > > + */ > > > + addr2 = rcu_access_pointer(*addr_p);/* Load A */ > > > > Why rcu_access_pointer() instead of READ_ONCE()? Because you want to > > mark the head of address dependency? > > Yes, the intent here is to mark the address dependency and provide > a publication guarantee similar to RCU pairing rcu_assign_pointer > and rcu_dereference. Do you see any reason why READ_ONCE() would > suffice here ? READ_ONCE() also provides address dependencies. See the "DEPENDENCY RELATIONS: data, addr, and ctrl" section in tools/memory-model/Documentation/explanantion.txt. Regards, Boqun > > Thanks, > > Mathieu >
Re: [RFC PATCH 3/4] hp: Implement Hazard Pointers
On 2024-10-03 02:24, Boqun Feng wrote: On Tue, Oct 01, 2024 at 09:02:04PM -0400, Mathieu Desnoyers wrote: [...] +/* + * hp_allocate: Allocate a hazard pointer. + * + * Allocate a hazard pointer slot for @addr. The object existence should + * be guaranteed by the caller. + * + * Returns a hazard pointer context. + */ +static inline +struct hp_ctx hp_allocate(struct hp_slot __percpu *percpu_slots, void *addr) +{ + struct hp_slot *slot; + struct hp_ctx ctx; + + if (!addr) + goto fail; + slot = this_cpu_ptr(percpu_slots); Are you assuming this is called with preemption disabled? Otherwise, there could two threads picking up the same hazard pointer slot on one CPU, Indeed, this minimalist implementation only covers the preempt-off use-case, where there is a single use of HP per CPU at any given time (e.g. for the lazy mm use-case). It expects to be called from preempt-off context. I will update the comment accordingly. + /* +* A single hazard pointer slot per CPU is available currently. +* Other hazard pointer domains can eventually have a different +* configuration. +*/ + if (READ_ONCE(slot->addr)) + goto fail; .. and they could both read an empty slot, and both think they successfully protect the objects, which could be different objects. Or am I missing something subtle here? You are correct, I should document this. + WRITE_ONCE(slot->addr, addr);/* Store B */ + ctx.slot = slot; + ctx.addr = addr; + return ctx; + +fail: + ctx.slot = NULL; + ctx.addr = NULL; + return ctx; +} + +/* + * hp_dereference_allocate: Dereference and allocate a hazard pointer. + * + * Returns a hazard pointer context. + */ +static inline +struct hp_ctx hp_dereference_allocate(struct hp_slot __percpu *percpu_slots, void * const * addr_p) +{ + struct hp_slot *slot; + void *addr, *addr2; + struct hp_ctx ctx; + + addr = READ_ONCE(*addr_p); +retry: + ctx = hp_allocate(percpu_slots, addr); + if (!hp_ctx_addr(ctx)) + goto fail; + /* Memory ordering: Store B before Load A. */ + smp_mb(); + /* +* Use RCU dereference without lockdep checks, because +* lockdep is not aware of HP guarantees. +*/ + addr2 = rcu_access_pointer(*addr_p);/* Load A */ Why rcu_access_pointer() instead of READ_ONCE()? Because you want to mark the head of address dependency? Yes, the intent here is to mark the address dependency and provide a publication guarantee similar to RCU pairing rcu_assign_pointer and rcu_dereference. Do you see any reason why READ_ONCE() would suffice here ? Thanks, Mathieu Regards, Boqun + /* +* If @addr_p content has changed since the first load, +* clear the hazard pointer and try again. +*/ + if (!ptr_eq(addr2, addr)) { + WRITE_ONCE(slot->addr, NULL); + if (!addr2) + goto fail; + addr = addr2; + goto retry; + } + ctx.slot = slot; + ctx.addr = addr2; + return ctx; + +fail: + ctx.slot = NULL; + ctx.addr = NULL; + return ctx; +} + [...] -- Mathieu Desnoyers EfficiOS Inc. https://www.efficios.com
Re: [RFC PATCH 3/4] hp: Implement Hazard Pointers
On Tue, Oct 01, 2024 at 09:02:04PM -0400, Mathieu Desnoyers wrote: > This API provides existence guarantees of objects through Hazard > Pointers (HP). > > Each HP domain defines a fixed number of hazard pointer slots (nr_cpus) > across the entire system. > > Its main benefit over RCU is that it allows fast reclaim of > HP-protected pointers without needing to wait for a grace period. > > It also allows the hazard pointer scan to call a user-defined callback > to retire a hazard pointer slot immediately if needed. This callback > may, for instance, issue an IPI to the relevant CPU. > > There are a few possible use-cases for this in the Linux kernel: > > - Improve performance of mm_count by replacing lazy active mm by HP. > - Guarantee object existence on pointer dereference to use refcount: > - replace locking used for that purpose in some drivers, > - replace RCU + inc_not_zero pattern, > - rtmutex: Improve situations where locks need to be taken in > reverse dependency chain order by guaranteeing existence of > first and second locks in traversal order, allowing them to be > locked in the correct order (which is reverse from traversal > order) rather than try-lock+retry on nested lock. > > References: > > [1]: M. M. Michael, "Hazard pointers: safe memory reclamation for > lock-free objects," in IEEE Transactions on Parallel and > Distributed Systems, vol. 15, no. 6, pp. 491-504, June 2004 > > Link: > https://lore.kernel.org/lkml/j3scdl5iymjlxavomgc6u5ndg3svhab6ga23dr36o4f5mt333w@7xslvq6b6hmv/ > Link: https://lpc.events/event/18/contributions/1731/ > Signed-off-by: Mathieu Desnoyers > Cc: Nicholas Piggin > Cc: Michael Ellerman > Cc: Greg Kroah-Hartman > Cc: Sebastian Andrzej Siewior > Cc: "Paul E. McKenney" > Cc: Will Deacon > Cc: Peter Zijlstra > Cc: Boqun Feng > Cc: Alan Stern > Cc: John Stultz > Cc: Neeraj Upadhyay > Cc: Linus Torvalds > Cc: Andrew Morton > Cc: Boqun Feng > Cc: Frederic Weisbecker > Cc: Joel Fernandes > Cc: Josh Triplett > Cc: Uladzislau Rezki > Cc: Steven Rostedt > Cc: Lai Jiangshan > Cc: Zqiang > Cc: Ingo Molnar > Cc: Waiman Long > Cc: Mark Rutland > Cc: Thomas Gleixner > Cc: Vlastimil Babka > Cc: maged.mich...@gmail.com > Cc: Mateusz Guzik > Cc: Jonas Oberhauser > Cc: r...@vger.kernel.org > Cc: linux...@kvack.org > Cc: l...@lists.linux.dev > --- > include/linux/hp.h | 154 + > kernel/Makefile| 2 +- > kernel/hp.c| 46 ++ > 3 files changed, 201 insertions(+), 1 deletion(-) > create mode 100644 include/linux/hp.h > create mode 100644 kernel/hp.c > > diff --git a/include/linux/hp.h b/include/linux/hp.h > new file mode 100644 > index ..929e8685a0fd > --- /dev/null > +++ b/include/linux/hp.h > @@ -0,0 +1,154 @@ > +// SPDX-FileCopyrightText: 2024 Mathieu Desnoyers > > +// > +// SPDX-License-Identifier: LGPL-2.1-or-later > + > +#ifndef _LINUX_HP_H > +#define _LINUX_HP_H > + > +/* > + * HP: Hazard Pointers > + * > + * This API provides existence guarantees of objects through hazard > + * pointers. > + * > + * It uses a fixed number of hazard pointer slots (nr_cpus) across the > + * entire system for each HP domain. > + * > + * Its main benefit over RCU is that it allows fast reclaim of > + * HP-protected pointers without needing to wait for a grace period. > + * > + * It also allows the hazard pointer scan to call a user-defined callback > + * to retire a hazard pointer slot immediately if needed. This callback > + * may, for instance, issue an IPI to the relevant CPU. > + * > + * References: > + * > + * [1]: M. M. Michael, "Hazard pointers: safe memory reclamation for > + * lock-free objects," in IEEE Transactions on Parallel and > + * Distributed Systems, vol. 15, no. 6, pp. 491-504, June 2004 > + */ > + > +#include > + > +/* > + * Hazard pointer slot. > + */ > +struct hp_slot { > + void *addr; > +}; > + > +/* > + * Hazard pointer context, returned by hp_use(). > + */ > +struct hp_ctx { > + struct hp_slot *slot; > + void *addr; > +}; > + > +/* > + * hp_scan: Scan hazard pointer domain for @addr. > + * > + * Scan hazard pointer domain for @addr. > + * If @retire_cb is NULL, wait to observe that each slot contains a value > + * that differs from @addr. > + * If @retire_cb is non-NULL, invoke @callback for each slot containing > + * @addr. > + */ > +void hp_scan(struct hp_slot __percpu *percpu_slots, void *addr, > + void (*retire_cb)(int cpu, struct hp_slot *slot, void *addr)); > + > +/* Get the hazard pointer context address (may be NULL). */ > +static inline > +void *hp_ctx_addr(struct hp_ctx ctx) > +{ > + return ctx.addr; > +} > + > +/* > + * hp_allocate: Allocate a hazard pointer. > + * > + * Allocate a hazard pointer slot for @addr. The object existence should > + * be guaranteed by the caller. > + * > + * Returns a hazard pointer context. > + */ > +static inline > +struct hp_ctx hp_alloc