Skip to content

Commit ac529d8

Browse files
Christoph Hellwigtehcaster
authored andcommitted
mempool: add mempool_{alloc,free}_bulk
Add a version of the mempool allocator that works for batch allocations of multiple objects. Calling mempool_alloc in a loop is not safe because it could deadlock if multiple threads are performing such an allocation at the same time. As an extra benefit the interface is build so that the same array can be used for alloc_pages_bulk / release_pages so that at least for page backed mempools the fast path can use a nice batch optimization. Note that mempool_alloc_bulk does not take a gfp_mask argument as it must always be able to sleep and doesn't support any non-trivial modifiers. NOFO or NOIO constrainst must be set through the scoped API. Signed-off-by: Christoph Hellwig <hch@lst.de> Link: https://patch.msgid.link/20251113084022.1255121-8-hch@lst.de Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
1 parent 1742d97 commit ac529d8

2 files changed

Lines changed: 141 additions & 42 deletions

File tree

include/linux/mempool.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,15 @@ extern void mempool_destroy(mempool_t *pool);
6666
extern void *mempool_alloc_noprof(mempool_t *pool, gfp_t gfp_mask) __malloc;
6767
#define mempool_alloc(...) \
6868
alloc_hooks(mempool_alloc_noprof(__VA_ARGS__))
69+
int mempool_alloc_bulk_noprof(struct mempool *pool, void **elem,
70+
unsigned int count, unsigned int allocated);
71+
#define mempool_alloc_bulk(...) \
72+
alloc_hooks(mempool_alloc_bulk_noprof(__VA_ARGS__))
6973

7074
extern void *mempool_alloc_preallocated(mempool_t *pool) __malloc;
7175
extern void mempool_free(void *element, mempool_t *pool);
76+
unsigned int mempool_free_bulk(struct mempool *pool, void **elem,
77+
unsigned int count);
7278

7379
/*
7480
* A mempool_alloc_t and mempool_free_t that get the memory from

mm/mempool.c

Lines changed: 135 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,21 @@
2121
#include "slab.h"
2222

2323
static DECLARE_FAULT_ATTR(fail_mempool_alloc);
24+
static DECLARE_FAULT_ATTR(fail_mempool_alloc_bulk);
2425

2526
static int __init mempool_faul_inject_init(void)
2627
{
27-
return PTR_ERR_OR_ZERO(fault_create_debugfs_attr("fail_mempool_alloc",
28+
int error;
29+
30+
error = PTR_ERR_OR_ZERO(fault_create_debugfs_attr("fail_mempool_alloc",
2831
NULL, &fail_mempool_alloc));
32+
if (error)
33+
return error;
34+
35+
/* booting will fail on error return here, don't bother to cleanup */
36+
return PTR_ERR_OR_ZERO(
37+
fault_create_debugfs_attr("fail_mempool_alloc_bulk", NULL,
38+
&fail_mempool_alloc_bulk));
2939
}
3040
late_initcall(mempool_faul_inject_init);
3141

@@ -380,15 +390,22 @@ int mempool_resize(mempool_t *pool, int new_min_nr)
380390
}
381391
EXPORT_SYMBOL(mempool_resize);
382392

383-
static void *mempool_alloc_from_pool(struct mempool *pool, gfp_t gfp_mask)
393+
static unsigned int mempool_alloc_from_pool(struct mempool *pool, void **elems,
394+
unsigned int count, unsigned int allocated,
395+
gfp_t gfp_mask)
384396
{
385397
unsigned long flags;
386-
void *element;
398+
unsigned int i;
387399

388400
spin_lock_irqsave(&pool->lock, flags);
389-
if (unlikely(!pool->curr_nr))
401+
if (unlikely(pool->curr_nr < count - allocated))
390402
goto fail;
391-
element = remove_element(pool);
403+
for (i = 0; i < count; i++) {
404+
if (!elems[i]) {
405+
elems[i] = remove_element(pool);
406+
allocated++;
407+
}
408+
}
392409
spin_unlock_irqrestore(&pool->lock, flags);
393410

394411
/* Paired with rmb in mempool_free(), read comment there. */
@@ -398,8 +415,9 @@ static void *mempool_alloc_from_pool(struct mempool *pool, gfp_t gfp_mask)
398415
* Update the allocation stack trace as this is more useful for
399416
* debugging.
400417
*/
401-
kmemleak_update_trace(element);
402-
return element;
418+
for (i = 0; i < count; i++)
419+
kmemleak_update_trace(elems[i]);
420+
return allocated;
403421

404422
fail:
405423
if (gfp_mask & __GFP_DIRECT_RECLAIM) {
@@ -421,7 +439,7 @@ static void *mempool_alloc_from_pool(struct mempool *pool, gfp_t gfp_mask)
421439
spin_unlock_irqrestore(&pool->lock, flags);
422440
}
423441

424-
return NULL;
442+
return allocated;
425443
}
426444

427445
/*
@@ -437,6 +455,65 @@ static inline gfp_t mempool_adjust_gfp(gfp_t *gfp_mask)
437455
return *gfp_mask & ~(__GFP_DIRECT_RECLAIM | __GFP_IO);
438456
}
439457

458+
/**
459+
* mempool_alloc_bulk - allocate multiple elements from a memory pool
460+
* @pool: pointer to the memory pool
461+
* @elems: partially or fully populated elements array
462+
* @count: number of entries in @elem that need to be allocated
463+
* @allocated: number of entries in @elem already allocated
464+
*
465+
* Allocate elements for each slot in @elem that is non-%NULL. This is done by
466+
* first calling into the alloc_fn supplied at pool initialization time, and
467+
* dipping into the reserved pool when alloc_fn fails to allocate an element.
468+
*
469+
* On return all @count elements in @elems will be populated.
470+
*
471+
* Return: Always 0. If it wasn't for %$#^$ alloc tags, it would return void.
472+
*/
473+
int mempool_alloc_bulk_noprof(struct mempool *pool, void **elems,
474+
unsigned int count, unsigned int allocated)
475+
{
476+
gfp_t gfp_mask = GFP_KERNEL;
477+
gfp_t gfp_temp = mempool_adjust_gfp(&gfp_mask);
478+
unsigned int i = 0;
479+
480+
VM_WARN_ON_ONCE(count > pool->min_nr);
481+
might_alloc(gfp_mask);
482+
483+
/*
484+
* If an error is injected, fail all elements in a bulk allocation so
485+
* that we stress the multiple elements missing path.
486+
*/
487+
if (should_fail_ex(&fail_mempool_alloc_bulk, 1, FAULT_NOWARN)) {
488+
pr_info("forcing mempool usage for %pS\n",
489+
(void *)_RET_IP_);
490+
goto use_pool;
491+
}
492+
493+
repeat_alloc:
494+
/*
495+
* Try to allocate the elements using the allocation callback first as
496+
* that might succeed even when the caller's bulk allocation did not.
497+
*/
498+
for (i = 0; i < count; i++) {
499+
if (elems[i])
500+
continue;
501+
elems[i] = pool->alloc(gfp_temp, pool->pool_data);
502+
if (unlikely(!elems[i]))
503+
goto use_pool;
504+
allocated++;
505+
}
506+
507+
return 0;
508+
509+
use_pool:
510+
allocated = mempool_alloc_from_pool(pool, elems, count, allocated,
511+
gfp_temp);
512+
gfp_temp = gfp_mask;
513+
goto repeat_alloc;
514+
}
515+
EXPORT_SYMBOL_GPL(mempool_alloc_bulk_noprof);
516+
440517
/**
441518
* mempool_alloc - allocate an element from a memory pool
442519
* @pool: pointer to the memory pool
@@ -478,8 +555,7 @@ void *mempool_alloc_noprof(mempool_t *pool, gfp_t gfp_mask)
478555
* sleep in mempool_alloc_from_pool. Retry the allocation
479556
* with all flags set in that case.
480557
*/
481-
element = mempool_alloc_from_pool(pool, gfp_temp);
482-
if (!element) {
558+
if (!mempool_alloc_from_pool(pool, &element, 1, 0, gfp_temp)) {
483559
if (gfp_temp != gfp_mask) {
484560
gfp_temp = gfp_mask;
485561
goto repeat_alloc;
@@ -508,26 +584,33 @@ EXPORT_SYMBOL(mempool_alloc_noprof);
508584
*/
509585
void *mempool_alloc_preallocated(mempool_t *pool)
510586
{
511-
return mempool_alloc_from_pool(pool, GFP_NOWAIT);
587+
void *element = NULL;
588+
589+
mempool_alloc_from_pool(pool, &element, 1, 0, GFP_NOWAIT);
590+
return element;
512591
}
513592
EXPORT_SYMBOL(mempool_alloc_preallocated);
514593

515594
/**
516-
* mempool_free - return an element to a mempool
517-
* @element: pointer to element
595+
* mempool_free_bulk - return elements to a mempool
518596
* @pool: pointer to the memory pool
597+
* @elems: elements to return
598+
* @count: number of elements to return
519599
*
520-
* Returns @element to @pool if it needs replenishing, else frees it using
521-
* the free_fn callback in @pool.
600+
* Returns a number of elements from the start of @elem to @pool if @pool needs
601+
* replenishing and sets their slots in @elem to NULL. Other elements are left
602+
* in @elem.
522603
*
523-
* This function only sleeps if the free_fn callback sleeps.
604+
* Return: number of elements transferred to @pool. Elements are always
605+
* transferred from the beginning of @elem, so the return value can be used as
606+
* an offset into @elem for the freeing the remaining elements in the caller.
524607
*/
525-
void mempool_free(void *element, mempool_t *pool)
608+
unsigned int mempool_free_bulk(struct mempool *pool, void **elems,
609+
unsigned int count)
526610
{
527611
unsigned long flags;
528-
529-
if (unlikely(element == NULL))
530-
return;
612+
unsigned int freed = 0;
613+
bool added = false;
531614

532615
/*
533616
* Paired with the wmb in mempool_alloc(). The preceding read is
@@ -561,42 +644,52 @@ void mempool_free(void *element, mempool_t *pool)
561644
* Waiters happen iff curr_nr is 0 and the above guarantee also
562645
* ensures that there will be frees which return elements to the
563646
* pool waking up the waiters.
564-
*/
565-
if (unlikely(READ_ONCE(pool->curr_nr) < pool->min_nr)) {
566-
spin_lock_irqsave(&pool->lock, flags);
567-
if (likely(pool->curr_nr < pool->min_nr)) {
568-
add_element(pool, element);
569-
spin_unlock_irqrestore(&pool->lock, flags);
570-
if (wq_has_sleeper(&pool->wait))
571-
wake_up(&pool->wait);
572-
return;
573-
}
574-
spin_unlock_irqrestore(&pool->lock, flags);
575-
}
576-
577-
/*
578-
* Handle the min_nr = 0 edge case:
579647
*
580648
* For zero-minimum pools, curr_nr < min_nr (0 < 0) never succeeds,
581649
* so waiters sleeping on pool->wait would never be woken by the
582650
* wake-up path of previous test. This explicit check ensures the
583651
* allocation of element when both min_nr and curr_nr are 0, and
584652
* any active waiters are properly awakened.
585653
*/
586-
if (unlikely(pool->min_nr == 0 &&
654+
if (unlikely(READ_ONCE(pool->curr_nr) < pool->min_nr)) {
655+
spin_lock_irqsave(&pool->lock, flags);
656+
while (pool->curr_nr < pool->min_nr && freed < count) {
657+
add_element(pool, elems[freed++]);
658+
added = true;
659+
}
660+
spin_unlock_irqrestore(&pool->lock, flags);
661+
} else if (unlikely(pool->min_nr == 0 &&
587662
READ_ONCE(pool->curr_nr) == 0)) {
663+
/* Handle the min_nr = 0 edge case: */
588664
spin_lock_irqsave(&pool->lock, flags);
589665
if (likely(pool->curr_nr == 0)) {
590-
add_element(pool, element);
591-
spin_unlock_irqrestore(&pool->lock, flags);
592-
if (wq_has_sleeper(&pool->wait))
593-
wake_up(&pool->wait);
594-
return;
666+
add_element(pool, elems[freed++]);
667+
added = true;
595668
}
596669
spin_unlock_irqrestore(&pool->lock, flags);
597670
}
598671

599-
pool->free(element, pool->pool_data);
672+
if (unlikely(added) && wq_has_sleeper(&pool->wait))
673+
wake_up(&pool->wait);
674+
675+
return freed;
676+
}
677+
EXPORT_SYMBOL_GPL(mempool_free_bulk);
678+
679+
/**
680+
* mempool_free - return an element to the pool.
681+
* @element: element to return
682+
* @pool: pointer to the memory pool
683+
*
684+
* Returns @element to @pool if it needs replenishing, else frees it using
685+
* the free_fn callback in @pool.
686+
*
687+
* This function only sleeps if the free_fn callback sleeps.
688+
*/
689+
void mempool_free(void *element, struct mempool *pool)
690+
{
691+
if (likely(element) && !mempool_free_bulk(pool, &element, 1))
692+
pool->free(element, pool->pool_data);
600693
}
601694
EXPORT_SYMBOL(mempool_free);
602695

0 commit comments

Comments
 (0)