[RFC PATCH 2/3] mm/zsmalloc: remove pool->lock from zs_free on 64-bit systems

Barry Song baohua at kernel.org
Mon May 11 18:15:27 PDT 2026


On Fri, May 8, 2026 at 2:19 PM Wenchao Hao <haowenchao22 at gmail.com> wrote:
>
> With class_idx now encoded in the obj value (ZS_OBJ_CLASS_IDX),
> zs_free() no longer needs pool->lock to locate the size class on
> 64-bit systems.
>
> The class_idx is invariant across page migration (only PFN changes),
> and 64-bit aligned reads are atomic, so a lockless read of the handle
> always yields a valid class_idx.  After acquiring class->lock (which
> blocks concurrent migration), the handle is re-read to obtain a stable
> PFN for the actual free operation.
>
> This eliminates rwlock read-side contention between zs_free() and page
> migration/compaction, improving zs_free() scalability on multi-core
> systems.
>
> On 32-bit systems (ZS_OBJ_CLASS_IDX not defined), the original
> pool->lock path is preserved.
>
> Signed-off-by: Wenchao Hao <haowenchao at xiaomi.com>
> ---
>  mm/zsmalloc.c | 23 +++++++++++++++++++++--
>  1 file changed, 21 insertions(+), 2 deletions(-)
>
> diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c
> index bccadf0a27f2..47ec0414ce9e 100644
> --- a/mm/zsmalloc.c
> +++ b/mm/zsmalloc.c
> @@ -21,6 +21,10 @@
>   *     pool->lock
>   *     class->lock
>   *     zspage->lock
> + *
> + * On 64-bit systems with ZS_OBJ_CLASS_IDX enabled, zs_free() does not
> + * take pool->lock; it extracts class_idx from the obj encoding with a
> + * lockless read, then re-reads obj under class->lock.
>   */
>
>  #include <linux/module.h>
> @@ -1467,10 +1471,24 @@ void zs_free(struct zs_pool *pool, unsigned long handle)
>         if (IS_ERR_OR_NULL((void *)handle))
>                 return;
>
> +#ifdef ZS_OBJ_CLASS_IDX
> +       /*
> +        * The class_idx encoded in obj is invariant across migration
> +        * (only PFN changes), and the read of *(unsigned long *)handle
> +        * is atomic on 64-bit, so we can determine the correct class
> +        * without holding pool->lock.
> +        */
> +       obj = handle_to_obj(handle);
> +       class = pool->size_class[obj_to_class_idx(obj)];
> +       spin_lock(&class->lock);
>         /*
> -        * The pool->lock protects the race with zpage's migration
> -        * so it's safe to get the page from handle.
> +        * Re-read under class->lock: migration also acquires class->lock,
> +        * so the obj value is now stable and the PFN is valid.
>          */
> +       obj = handle_to_obj(handle);
> +       obj_to_zpdesc(obj, &f_zpdesc);
> +       zspage = get_zspage(f_zpdesc);
> +#else
>         read_lock(&pool->lock);
>         obj = handle_to_obj(handle);
>         obj_to_zpdesc(obj, &f_zpdesc);
> @@ -1478,6 +1496,7 @@ void zs_free(struct zs_pool *pool, unsigned long handle)
>         class = zspage_class(pool, zspage);
>         spin_lock(&class->lock);
>         read_unlock(&pool->lock);
> +#endif

This looks a bit inelegant. Can we extract a helper like
obj_handle_class_lock(), or something similar? It could have
different implementations for 32-bit and 64-bit builds.

>
>         class_stat_sub(class, ZS_OBJS_INUSE, 1);
>         obj_free(class->size, obj);
> --
> 2.34.1
>



More information about the linux-riscv mailing list