ublk: avoid unpinning pages under maple tree spinlock

ublk_shmem_remove_ranges() calls unpin_user_pages() while holding the
maple tree spinlock (mas_lock). Although unpin_user_pages() is safe in
atomic context, holding the spinlock across potentially many page
unpinning operations is not ideal.

Split into __ublk_shmem_remove_ranges() which erases up to 64 ranges
under mas_lock, collecting base_pfn and nr_pages into a temporary
xarray. Then drop the lock and unpin pages outside spinlock context.
ublk_shmem_remove_ranges() loops until all matching ranges are
processed.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
Link: https://patch.msgid.link/20260423033058.2805135-4-tom.leiming@gmail.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Ming Lei 2026-04-23 11:30:58 +08:00 committed by Jens Axboe
parent ea1db795de
commit 309e02dccf

View File

@ -5441,32 +5441,68 @@ static void ublk_unpin_range_pages(unsigned long base_pfn,
}
/*
* Remove ranges from the maple tree matching buf_index, unpin pages
* and free range structs. If buf_index < 0, remove all ranges.
* Inner loop: erase up to UBLK_REMOVE_BATCH matching ranges under
* mas_lock, collecting them into an xarray. Then drop the lock and
* unpin pages + free ranges outside spinlock context.
*
* Returns true if the tree walk completed, false if more ranges remain.
* Xarray key is the base PFN, value encodes nr_pages via xa_mk_value().
*/
static int ublk_shmem_remove_ranges(struct ublk_device *ub, int buf_index)
#define UBLK_REMOVE_BATCH 64
static bool __ublk_shmem_remove_ranges(struct ublk_device *ub,
int buf_index, int *ret)
{
MA_STATE(mas, &ub->buf_tree, 0, ULONG_MAX);
struct ublk_buf_range *range;
int ret = -ENOENT;
struct xarray to_unpin;
unsigned long idx;
unsigned int count = 0;
bool done = false;
void *entry;
xa_init(&to_unpin);
mas_lock(&mas);
mas_for_each(&mas, range, ULONG_MAX) {
unsigned long base, nr;
unsigned long nr;
if (buf_index >= 0 && range->buf_index != buf_index)
continue;
ret = 0;
base = mas.index;
nr = mas.last - base + 1;
*ret = 0;
nr = mas.last - mas.index + 1;
if (xa_err(xa_store(&to_unpin, mas.index,
xa_mk_value(nr), GFP_ATOMIC)))
goto unlock;
mas_erase(&mas);
ublk_unpin_range_pages(base, nr);
kfree(range);
if (++count >= UBLK_REMOVE_BATCH)
goto unlock;
}
done = true;
unlock:
mas_unlock(&mas);
xa_for_each(&to_unpin, idx, entry)
ublk_unpin_range_pages(idx, xa_to_value(entry));
xa_destroy(&to_unpin);
return done;
}
/*
* Remove ranges from the maple tree matching buf_index, unpin pages
* and free range structs. If buf_index < 0, remove all ranges.
* Processes ranges in batches to avoid holding the maple tree spinlock
* across potentially expensive page unpinning.
*/
static int ublk_shmem_remove_ranges(struct ublk_device *ub, int buf_index)
{
int ret = -ENOENT;
while (!__ublk_shmem_remove_ranges(ub, buf_index, &ret))
cond_resched();
return ret;
}