On Tue, Sep 22, 2026 at 4:28 PM Boqun Feng <[email protected]> wrote: > > On Tue, Sep 22, 2026 at 03:09:47PM +0800, Kunwu Chan wrote: > > Batch concurrent hazptr_synchronize() callers into a shared scan > > cycle, avoiding redundant scans of the per-CPU slots. > > > > Queue waiters to a kthread and let each scan cycle make one pass > > over all CPUs. Each waiter tracks per-CPU progress for both > > wildcard generations, allowing multiple waiters to share the same > > scan. > > > > Flip the wildcard before scanning. New acquires then use the new > > generation, so the old-generation mask makes forward progress even > > under a steady stream of readers. Waiters that remain blocked are > > retried after a short delay. > > > > Fall back to the existing direct two-phase scan if the scan kthread > > is unavailable or waiter state cannot be allocated. > > > > Signed-off-by: Kunwu Chan <[email protected]> > > --- > > kernel/hazptr.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++++ > > 1 file changed, 274 insertions(+) > > > > diff --git a/kernel/hazptr.c b/kernel/hazptr.c > > index d3d1050d92cf..ce553a61b119 100644 > > --- a/kernel/hazptr.c > > +++ b/kernel/hazptr.c > > @@ -12,6 +12,10 @@ > > #include <linux/mutex.h> > > #include <linux/list.h> > > #include <linux/export.h> > > +#include <linux/completion.h> > > +#include <linux/kthread.h> > > +#include <linux/slab.h> > > +#include <linux/swait.h> > > > > /* > > * The current hazard pointer wildcard. Flips between 1UL and 2UL to > > guarantee > > @@ -209,12 +213,251 @@ void hazptr_scan_period(void *addr, void > > *scan_wildcard) > > } > > } > > > > +/* > > + * Batch hazptr_synchronize() callers through a shared scan kthread. > > + */ > > + > > +struct hazptr_waiter { > > + struct list_head node; > > + void *addr; > > + struct completion done; > > + /* > > + * Per-wildcard-generation progress masks. A CPU bit is > > + * cleared when the scan observes neither @addr nor that > > + * generation's wildcard on the CPU. > > + */ > > + unsigned long *cpu_mask; /* 2 * BITS_TO_LONGS(nr_cpu_ids) */ > > This would requires allocation during hazptr_synchronize() and I would > like to avoid that (it's going to introduce a "allocating memory to free > memory" case). >
Thanks boqun, this is a useful direction. > Mathieu brought up a useful data structure for the scan: A Bloom filter: > > https://en.wikipedia.org/wiki/Bloom_filter > > , which is basically a bitmap set + k hash functions. Let's say we have > a struct bloom_filter (you can still with a page as the bitmap and k=3) > and put it in hazptr_scan_state. Then the scan would become: > > bloom_filter_clear(); // <- reset the bloom filer. > > for_each_possible_cpu() > hlist_for_each_entry(b, &list->head, overflow_node) { > bloom_filter_set(*b->slot.addr); > // ^ add the hazptr_acquire() adress into the bloom filter > } > > list_for_each_entry(w, &hazptr_scan.scanning, node) { > if (!bloom_filter_contains(w->addr)) { > list_move(&w->node, &done); > } > } > > Of course, there are some additional handling or optimizaiton we can do > with the per-CPU slot and wildcard, but this is the idea. It also makes > a potential call_hazptr() work. > > Willing to give it a try? Sure, I'll give it a try. I'll rework the scan state around the Bloom filter and address the per-CPU slot and wildcard handling in the next version. I'll also credit your 2025 patch design in the commit message. Thanks, Kunwu > > Regards, > Boqun > > > +}; > > + > > +/* Return waiter @w's progress mask for wildcard generation @gen. */ > > +static unsigned long *hazptr_waiter_mask(struct hazptr_waiter *w, int gen) > > +{ > > + return w->cpu_mask + gen * BITS_TO_LONGS(nr_cpu_ids); > > +} > > + > > +struct hazptr_scan_state { > > + struct task_struct *kthread; > > + struct swait_queue_head wq; > > + bool wakeup; > > + struct mutex lock; > > + struct list_head pending; > > + struct list_head scanning; /* kthread only */ > > +}; > > +static struct hazptr_scan_state hazptr_scan; > > + > > +/* > > + * Check a CPU's overflow lists. A backup slot can hold a wildcard > > + * because __hazptr_acquire() writes the wildcard to any slot, > > + * including backup slots from hazptr_chain_backup_slot(). > > + * > > + * @addr: address the waiter is waiting on > > + * @old_wc: wildcard value of the pre-flip generation > > + * @new_wc: wildcard value of the post-flip generation > > + * @has_old: set if any overflow slot holds @old_wc > > + * @has_new: set if any overflow slot holds @new_wc > > + * > > + * Returns true if @addr is present. > > + */ > > +static bool hazptr_ovf_list_blocked(int cpu, void *addr, > > + void *old_wc, void *new_wc, > > + bool *has_old, bool *has_new) > > +{ > > + struct hazptr_overflow_list_flip *ovf = > > per_cpu_ptr(&percpu_overflow_list_flip, cpu); > > + bool found_addr = false; > > + int i; > > + > > + for (i = 0; i < 2; i++) { > > + struct hazptr_overflow_list *list = &ovf->array[i]; > > + struct hazptr_backup_slot *b; > > + unsigned long flags; > > + > > + raw_spin_lock_irqsave(&list->lock, flags); > > + hlist_for_each_entry(b, &list->head, overflow_node) { > > + /* Pairs with smp_store_release in hazptr_release(). > > */ > > + void *val = smp_load_acquire(&b->slot.addr); > > + > > + if (val == addr) > > + found_addr = true; > > + else if (val == old_wc) > > + *has_old = true; > > + else if (val == new_wc) > > + *has_new = true; > > + } > > + raw_spin_unlock_irqrestore(&list->lock, flags); > > + } > > + return found_addr; > > +} > > + > > +/* > > + * Move pending waiters to ->scanning, flip the wildcard, then make > > + * one pass over all CPUs. Clear per-waiter bits for CPUs that no > > + * longer hold the waiter address or the corresponding wildcard. > > + * > > + * After the flip, new acquires use the new wildcard. The old > > + * generation therefore makes forward progress and is fully cleared > > + * after enough scan cycles. > > + */ > > +static void hazptr_scan_do_cycle(void) > > +{ > > + void *old_wc, *new_wc; > > + unsigned int old_idx, new_idx; > > + int cpu; > > + struct hazptr_waiter *w, *n; > > + LIST_HEAD(done); > > + > > + mutex_lock(&hazptr_wildcard_lock); > > + > > + mutex_lock(&hazptr_scan.lock); > > + list_splice_tail_init(&hazptr_scan.pending, &hazptr_scan.scanning); > > + mutex_unlock(&hazptr_scan.lock); > > + > > + if (list_empty(&hazptr_scan.scanning)) { > > + mutex_unlock(&hazptr_wildcard_lock); > > + return; > > + } > > + > > + old_wc = READ_ONCE(hazptr_wildcard); > > + new_wc = flip_wildcard(old_wc); > > + WRITE_ONCE(hazptr_wildcard, new_wc); > > + old_idx = (unsigned long)old_wc - 1; > > + new_idx = 1 - old_idx; > > + > > + /* > > + * One pass over all CPUs for the per-CPU slots, checking > > + * overflow lists for the remaining waiters. > > + */ > > + for_each_possible_cpu(cpu) { > > + struct hazptr_percpu_slots *slots = > > per_cpu_ptr(&hazptr_percpu_slots, cpu); > > + void *vals[NR_HAZPTR_PERCPU_SLOTS]; > > + bool has_old = false, has_new = false; > > + unsigned int idx; > > + > > + for (idx = 0; idx < NR_HAZPTR_PERCPU_SLOTS; idx++) { > > + /* Pairs with smp_store_release in hazptr_release(). > > */ > > + vals[idx] = > > smp_load_acquire(&slots->items[idx].slot.addr); > > + if (vals[idx] == old_wc) > > + has_old = true; > > + else if (vals[idx] == new_wc) > > + has_new = true; > > + } > > + > > + list_for_each_entry(w, &hazptr_scan.scanning, node) { > > + bool has_addr = false; > > + > > + if (!test_bit(cpu, hazptr_waiter_mask(w, old_idx)) && > > + !test_bit(cpu, hazptr_waiter_mask(w, new_idx))) > > + continue; /* Both bits already clear. */ > > + for (idx = 0; idx < NR_HAZPTR_PERCPU_SLOTS; idx++) { > > + if (vals[idx] == w->addr) { > > + has_addr = true; > > + break; > > + } > > + } > > + if (!has_addr) > > + has_addr = hazptr_ovf_list_blocked(cpu, > > w->addr, > > + old_wc, new_wc, &has_old, &has_new); > > + if (has_addr) > > + continue; > > + if (!has_old) > > + __clear_bit(cpu, hazptr_waiter_mask(w, > > old_idx)); > > + if (!has_new) > > + __clear_bit(cpu, hazptr_waiter_mask(w, > > new_idx)); > > + } > > + } > > + > > + mutex_unlock(&hazptr_wildcard_lock); > > + > > + /* Complete waiters whose masks are both empty. */ > > + list_for_each_entry_safe(w, n, &hazptr_scan.scanning, node) { > > + if (bitmap_empty(hazptr_waiter_mask(w, 0), nr_cpu_ids) && > > + bitmap_empty(hazptr_waiter_mask(w, 1), nr_cpu_ids)) > > + list_move(&w->node, &done); > > + } > > + > > + list_for_each_entry_safe(w, n, &done, node) { > > + list_del_init(&w->node); > > + complete(&w->done); > > + } > > +} > > + > > +/* > > + * Shared scan kthread for hazptr_synchronize() waiters. > > + */ > > +static int hazptr_scan_kthread(void *unused) > > +{ > > + for (;;) { > > + bool idle; > > + > > + swait_event_idle_exclusive(hazptr_scan.wq, > > + READ_ONCE(hazptr_scan.wakeup)); > > + > > + hazptr_scan_do_cycle(); > > + > > + mutex_lock(&hazptr_scan.lock); > > + idle = list_empty(&hazptr_scan.pending) && > > + list_empty(&hazptr_scan.scanning); > > + if (idle) > > + WRITE_ONCE(hazptr_scan.wakeup, false); > > + mutex_unlock(&hazptr_scan.lock); > > + > > + if (idle) > > + continue; > > + /* Waiters still blocked: retry after a polling delay. */ > > + schedule_timeout_idle(1); > > + } > > + return 0; > > +} > > + > > +/* > > + * Queue @addr for scan-thread processing, then sleep until the scan > > + * thread observes that @addr is no longer held by any hazard pointer. > > + * Returns false if the waiter masks cannot be allocated, in which > > + * case the caller falls back to the direct scan. > > + */ > > +static bool hazptr_synchronize_queued(void *addr) > > +{ > > + struct hazptr_waiter waiter = { > > + .addr = addr, > > + }; > > + unsigned long *masks; > > + unsigned int mask_longs = BITS_TO_LONGS(nr_cpu_ids); > > + > > + masks = kcalloc(2, mask_longs * sizeof(unsigned long), GFP_KERNEL); > > + if (!masks) > > + return false; > > + bitmap_fill(masks, nr_cpu_ids); > > + bitmap_fill(masks + mask_longs, nr_cpu_ids); > > + waiter.cpu_mask = masks; > > + > > + init_completion(&waiter.done); > > + INIT_LIST_HEAD(&waiter.node); > > + > > + /* Enqueue and wake the scan kthread. */ > > + mutex_lock(&hazptr_scan.lock); > > + list_add_tail(&waiter.node, &hazptr_scan.pending); > > + if (!READ_ONCE(hazptr_scan.wakeup)) { > > + WRITE_ONCE(hazptr_scan.wakeup, true); > > + swake_up_one(&hazptr_scan.wq); > > + } > > + mutex_unlock(&hazptr_scan.lock); > > + > > + /* Sleep until the scan thread completes this waiter. */ > > + wait_for_completion(&waiter.done); > > + kfree(masks); > > + return true; > > +} > > + > > /* > > * hazptr_synchronize: Wait until @addr is released from all slots. > > * > > * Wait to observe that each slot contains a value that differs from > > * @addr before returning. > > * Should be called from preemptible context. > > + * > > + * If the scan kthread is running, the caller is queued and the scan > > + * thread performs the work, allowing multiple concurrent callers to > > + * share a single scan cycle. Otherwise, the existing direct > > + * two-phase scan is used as a fallback. > > */ > > void hazptr_synchronize(void *addr) > > { > > @@ -235,6 +478,13 @@ void hazptr_synchronize(void *addr) > > /* Memory ordering: Store A before Load B. */ > > smp_mb(); > > > > + /* Use the scan thread if available. */ > > + /* Pairs with smp_store_release in hazptr_scan_init(). */ > > + if (smp_load_acquire(&hazptr_scan.kthread) && > > + hazptr_synchronize_queued(addr)) > > + return; > > + > > + /* Fallback: direct two-phase wildcard scan. */ > > guard(mutex)(&hazptr_wildcard_lock); > > scan_wildcard = flip_wildcard(hazptr_wildcard); > > hazptr_scan_period(addr, scan_wildcard); > > @@ -282,3 +532,27 @@ void __init hazptr_init(void) > > } > > } > > } > > + > > +/* > > + * Initialize the scan kthread. On failure falls back to the direct > > + * scan (busy-wait) path at synchronize time. > > + * core_initcall ensures the scheduler is ready before kthread_run. > > + */ > > +static int __init hazptr_scan_init(void) > > +{ > > + struct task_struct *t; > > + > > + init_swait_queue_head(&hazptr_scan.wq); > > + mutex_init(&hazptr_scan.lock); > > + INIT_LIST_HEAD(&hazptr_scan.pending); > > + INIT_LIST_HEAD(&hazptr_scan.scanning); > > + > > + t = kthread_run(hazptr_scan_kthread, NULL, "hazptr_scan"); > > + if (!IS_ERR(t)) > > + /* Pairs with smp_load_acquire in hazptr_synchronize(). */ > > + smp_store_release(&hazptr_scan.kthread, t); > > + else > > + pr_warn("hazptr: scan thread failed, using direct scan\n"); > > + return 0; > > +} > > +core_initcall(hazptr_scan_init); > > -- > > 2.43.0 > >

