Viewing: page_pools.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2011, 2017, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <lustre_compat/linux/shrinker.h>
#include <lustre_compat/linux/mm.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lustre_dlm.h>
#include <lustre_sec.h>
#include <lustre_compat.h>
/* We have a pool for every power of 2 number of pages. Each pool must
* be able to provide at least one object of PTLRPC_MAX_BRW_SIZE.
* Most pools will be unused, but that's OK - unused pools are very cheap.
*/
#define POOLS_COUNT (PTLRPC_MAX_BRW_BITS - PAGE_SHIFT)
#define PAGES_TO_MiB(pages) ((pages) >> (20 - PAGE_SHIFT))
#define MiB_TO_PAGES(mb) ((mb) << (20 - PAGE_SHIFT))
/* deprecated - see pool_max_memory_mb below */
static int enc_pool_max_memory_mb;
module_param(enc_pool_max_memory_mb, int, 0644);
MODULE_PARM_DESC(enc_pool_max_memory_mb,
"Encoding pool max memory (MB), default unlimited (deprecated, please use pool_max_memory_mb)");
static int pool_max_memory_mb;
module_param(pool_max_memory_mb, int, 0644);
MODULE_PARM_DESC(pool_max_memory_mb,
"Encoding pool max memory (MB), default unlimited");
/*
* lustre page pools
*/
#define PTRS_PER_PAGE (PAGE_SIZE / sizeof(void *))
#define IDLE_IDX_MAX (100)
#define IDLE_IDX_WEIGHT (3)
#define CACHE_QUIESCENT_PERIOD (20)
static struct obd_page_pool {
unsigned long opp_max_objects; /* maximum objects can hold, const */
unsigned int opp_max_ptr_pages; /* number of ptr_pages, const */
/*
* wait queue in case of not enough free pages.
*/
wait_queue_head_t opp_waitq; /* waiting threads */
unsigned int opp_waitqlen; /* wait queue length */
unsigned long opp_pages_short; /* # of pages wanted of in-q users */
unsigned int opp_growing:1; /* during adding pages */
unsigned int opp_order; /* page pool order and index in pools
* array (element size is 2^order pages),
*/
/*
* indicating how idle the pool is, from 0 to MAX_IDLE_IDX
* this is counted based on each time when getting pages from
* the pool, not based on time. which means in case that system
* is idled for a while but the idle_idx might still be low if no
* activities happened in the pool.
*/
unsigned long opp_idle_idx;
/* last shrink time due to mem tight */
time64_t opp_last_shrink;
time64_t opp_last_access;
/* in-pool pages bookkeeping */
spinlock_t opp_lock; /* protect following fields */
unsigned long opp_total_objects; /* total objects in pool */
unsigned long opp_free_objects; /* current objects available */
/* statistics */
unsigned long opp_st_max_objects; /* # of objects ever reached */
unsigned int opp_st_grows; /* # of grows */
unsigned int opp_st_grow_fails; /* # of add pages failures */
unsigned int opp_st_shrinks; /* # of shrinks */
unsigned long opp_st_access; /* # of access */
unsigned long opp_st_missings; /* # of cache missing */
unsigned long opp_st_lowfree; /* lowest free objects reached */
unsigned int opp_st_max_wqlen; /* highest waitqueue length */
ktime_t opp_st_max_wait; /* in nanoseconds */
unsigned long opp_st_outofmem; /* # of out of mem requests */
/*
* pointers to ptr_pages, may be vmalloc'd
*/
void ***opp_ptr_pages;
/*
* memory shrinker
*/
struct shrinker *pool_shrinker;
struct mutex add_pages_mutex;
} **page_pools;
static struct shrinker **pool_shrinkers;
/* We try to allocate POOLS_COUNT pools,
* unless the node's memory is too short.
*/
static int pools_count = POOLS_COUNT;
static inline int get_pool_index(struct shrinker *shrinker)
{
int i;
for (i = 0; i < pools_count; i++)
if (pool_shrinkers[i] == shrinker)
return i;
CERROR("Shrinker %p has not been found among %i pools\n",
shrinker, POOLS_COUNT);
LBUG();
return -1;
}
static int object_size(struct obd_page_pool *pool)
{
return PAGE_SIZE << pool->opp_order;
}
/*
* Keep old name (encrypt_page_pool vs page_pool) for compatibility with user
* tools pulling stats
*
* /sys/kernel/debug/lustre/sptlrpc/encrypt_page_pools
*/
int encrypt_page_pools_seq_show(struct seq_file *m, void *v)
{
struct obd_page_pool *pool = page_pools[0];
spin_lock(&pool->opp_lock);
seq_printf(m,
"physical pages: %lu\n"
"pages per pool: %lu\n"
"max objects: %lu\n"
"max pools: %u\n"
"total objects: %lu\n"
"total free: %lu\n"
"idle index: %lu/100\n"
"last shrink: %llds\n"
"last access: %llds\n"
"max objects reached: %lu\n"
"grows: %u\n"
"grows failure: %u\n"
"shrinks: %u\n"
"cache access: %lu\n"
"cache missing: %lu\n"
"low free mark: %lu\n"
"max waitqueue depth: %u\n"
"max wait time ms: %lld\n"
"out of mem: %lu\n",
compat_totalram_pages(), PTRS_PER_PAGE,
pool->opp_max_objects,
pool->opp_max_ptr_pages,
pool->opp_total_objects,
pool->opp_free_objects,
pool->opp_idle_idx,
ktime_get_seconds() - pool->opp_last_shrink,
ktime_get_seconds() - pool->opp_last_access,
pool->opp_st_max_objects,
pool->opp_st_grows,
pool->opp_st_grow_fails,
pool->opp_st_shrinks,
pool->opp_st_access,
pool->opp_st_missings,
pool->opp_st_lowfree,
pool->opp_st_max_wqlen,
ktime_to_ms(pool->opp_st_max_wait),
pool->opp_st_outofmem);
spin_unlock(&pool->opp_lock);
return 0;
}
EXPORT_SYMBOL(encrypt_page_pools_seq_show);
/*
* /sys/kernel/debug/lustre/sptlrpc/page_pools
*/
int page_pools_seq_show(struct seq_file *m, void *v)
{
int pool_order;
struct obd_page_pool *pool;
seq_printf(m, "physical_pages: %lu\n"
"pools:\n",
compat_totalram_pages());
for (pool_order = 0; pool_order < pools_count; pool_order++) {
pool = page_pools[pool_order];
if (!pool->opp_st_access)
continue;
spin_lock(&pool->opp_lock);
seq_printf(m, " pool_%dk:\n"
" max_objects: %lu\n"
" max_items: %lu\n"
" total_objects: %lu\n"
" total_free: %lu\n"
" idle_index: %lu/100\n"
" last_shrink: %llds\n"
" last_access: %llds\n"
" max_objects_reached: %lu\n"
" grows: %u\n"
" grows_failure: %u\n"
" shrinks: %u\n"
" cache_access: %lu\n"
" cache_missing: %lu\n"
" low_free_mark: %lu\n"
" max_waitqueue_depth: %u\n"
" max_wait_time_ms: %lld\n"
" out_of_mem: %lu\n",
/* convert from bytes to KiB */
object_size(pool) >> 10,
pool->opp_max_objects,
pool->opp_max_ptr_pages * PTRS_PER_PAGE,
pool->opp_total_objects,
pool->opp_free_objects,
pool->opp_idle_idx,
ktime_get_seconds() - pool->opp_last_shrink,
ktime_get_seconds() - pool->opp_last_access,
pool->opp_st_max_objects,
pool->opp_st_grows,
pool->opp_st_grow_fails,
pool->opp_st_shrinks,
pool->opp_st_access,
pool->opp_st_missings,
pool->opp_st_lowfree,
pool->opp_st_max_wqlen,
ktime_to_ms(pool->opp_st_max_wait),
pool->opp_st_outofmem);
spin_unlock(&pool->opp_lock);
}
return 0;
}
EXPORT_SYMBOL(page_pools_seq_show);
static void pool_release_free_objects(long nobjects, struct obd_page_pool *pool)
{
int p_idx, g_idx;
int p_idx_max1, p_idx_max2;
LASSERT(nobjects > 0);
LASSERT(nobjects <= pool->opp_free_objects);
LASSERT(pool->opp_free_objects <= pool->opp_total_objects);
/* max pool index before the release */
p_idx_max2 = (pool->opp_total_objects - 1) / PTRS_PER_PAGE;
pool->opp_free_objects -= nobjects;
pool->opp_total_objects -= nobjects;
/* max pool index after the release */
p_idx_max1 = pool->opp_total_objects == 0 ? -1 :
((pool->opp_total_objects - 1) / PTRS_PER_PAGE);
p_idx = pool->opp_free_objects / PTRS_PER_PAGE;
g_idx = pool->opp_free_objects % PTRS_PER_PAGE;
LASSERT(pool->opp_ptr_pages[p_idx]);
while (nobjects--) {
LASSERT(pool->opp_ptr_pages[p_idx]);
LASSERT(pool->opp_ptr_pages[p_idx][g_idx] != NULL);
if (pool->opp_order == 0)
__free_page(pool->opp_ptr_pages[p_idx][g_idx]);
else
OBD_FREE_LARGE(pool->opp_ptr_pages[p_idx][g_idx],
object_size(pool));
pool->opp_ptr_pages[p_idx][g_idx] = NULL;
if (++g_idx == PTRS_PER_PAGE) {
p_idx++;
g_idx = 0;
}
}
/* free unused ptr_pages */
while (p_idx_max1 < p_idx_max2) {
LASSERT(pool->opp_ptr_pages[p_idx_max2]);
OBD_FREE(pool->opp_ptr_pages[p_idx_max2], PAGE_SIZE);
pool->opp_ptr_pages[p_idx_max2] = NULL;
p_idx_max2--;
}
}
/*
* we try to keep at least PTLRPC_MAX_BRW_PAGES pages in the pool.
*/
static unsigned long pool_shrink_count(struct shrinker *s,
struct shrink_control *sc)
{
int pool_order;
struct obd_page_pool *pool;
unsigned long max_objects;
pool_order = get_pool_index(s);
pool = page_pools[pool_order];
max_objects = PTLRPC_MAX_BRW_PAGES >> pool_order;
/* Always have at least one element */
if (max_objects == 0)
max_objects = 1;
/*
* if no pool access for a long time, we consider it's fully
* idle. A little race here is fine.
*/
if (pool->opp_idle_idx != IDLE_IDX_MAX &&
unlikely(ktime_get_seconds() - pool->opp_last_access >
CACHE_QUIESCENT_PERIOD)) {
spin_lock(&pool->opp_lock);
pool->opp_idle_idx = IDLE_IDX_MAX;
spin_unlock(&pool->opp_lock);
}
LASSERT(pool->opp_idle_idx <= IDLE_IDX_MAX);
return (pool->opp_free_objects <= max_objects) ? 0 :
(pool->opp_free_objects - max_objects) *
pool->opp_idle_idx / IDLE_IDX_MAX;
}
/*
* we try to keep at least PTLRPC_MAX_BRW_PAGES pages in the pool.
*/
static unsigned long pool_shrink_scan(struct shrinker *s,
struct shrink_control *sc)
{
int pool_order;
struct obd_page_pool *pool;
unsigned long max_objects;
pool_order = get_pool_index(s);
pool = page_pools[pool_order];
max_objects = PTLRPC_MAX_BRW_PAGES >> pool_order;
/* Always have at least one element */
if (max_objects == 0)
max_objects = 1;
spin_lock(&pool->opp_lock);
if (pool->opp_free_objects <= max_objects)
sc->nr_to_scan = 0;
else
sc->nr_to_scan = min_t(unsigned long, sc->nr_to_scan,
pool->opp_free_objects - max_objects);
if (sc->nr_to_scan > 0) {
pool_release_free_objects(sc->nr_to_scan, pool);
pool->opp_st_shrinks++;
pool->opp_last_shrink = ktime_get_seconds();
}
spin_unlock(&pool->opp_lock);
if (sc->nr_to_scan > 0)
CDEBUG(D_SEC, "released %lu objects, %ld left, order:%u\n",
sc->nr_to_scan, pool->opp_free_objects, pool->opp_order);
return sc->nr_to_scan;
}
static inline
int nobjects_to_nptr_pages(unsigned long nobjects)
{
return (int) ((nobjects + PTRS_PER_PAGE - 1) / PTRS_PER_PAGE);
}
/*
* return how many objects cleaned up.
*/
static unsigned long pool_cleanup(void ***ptr_pages, int nptr_pages,
struct obd_page_pool *pool)
{
unsigned long cleaned = 0;
int i, j;
for (i = 0; i < nptr_pages; i++) {
if (ptr_pages[i]) {
for (j = 0; j < PTRS_PER_PAGE; j++) {
if (ptr_pages[i][j]) {
if (pool->opp_order == 0) {
__free_page(ptr_pages[i][j]);
} else {
OBD_FREE_LARGE(ptr_pages[i][j],
object_size(pool));
}
cleaned++;
}
}
OBD_FREE(ptr_pages[i], PAGE_SIZE);
ptr_pages[i] = NULL;
}
}
return cleaned;
}
/*
* merge @nptr_objects pointed by @ptr_pages which contains @nobjects
* new objects into current pool.
*
* we have options to avoid most memory copy with some tricks. but we choose
* the simplest way to avoid complexity. It's not frequently called.
*/
static void pool_insert_ptrs(void ***ptr_pages, int nptr_pages, int nobjects,
struct obd_page_pool *page_pool)
{
int freeslot;
int op_idx, np_idx, og_idx, ng_idx;
int cur_nptr_page, end_nptr_page;
LASSERT(nobjects > 0);
LASSERT(page_pool->opp_total_objects + nobjects <=
page_pool->opp_max_objects);
LASSERT(nobjects_to_nptr_pages(nobjects) == nptr_pages);
LASSERT(page_pool->opp_growing);
spin_lock(&page_pool->opp_lock);
/*
* (1) fill all the free slots in current pool ptr_pages
*/
/*
* free slots are those left by rent pages, and the extra ones with
* index >= total_pages, locate at the tail of last pool.
*/
freeslot = page_pool->opp_total_objects % PTRS_PER_PAGE;
if (freeslot != 0)
freeslot = PTRS_PER_PAGE - freeslot;
freeslot += page_pool->opp_total_objects - page_pool->opp_free_objects;
op_idx = page_pool->opp_free_objects / PTRS_PER_PAGE;
og_idx = page_pool->opp_free_objects % PTRS_PER_PAGE;
np_idx = nptr_pages - 1;
ng_idx = (nobjects - 1) % PTRS_PER_PAGE;
while (freeslot) {
LASSERT(page_pool->opp_ptr_pages[op_idx][og_idx] == NULL);
LASSERT(ptr_pages[np_idx][ng_idx] != NULL);
page_pool->opp_ptr_pages[op_idx][og_idx] =
ptr_pages[np_idx][ng_idx];
ptr_pages[np_idx][ng_idx] = NULL;
freeslot--;
if (++og_idx == PTRS_PER_PAGE) {
op_idx++;
og_idx = 0;
}
if (--ng_idx < 0) {
if (np_idx == 0)
break;
np_idx--;
ng_idx = PTRS_PER_PAGE - 1;
}
}
/*
* (2) add ptr pages if needed.
*/
cur_nptr_page = (page_pool->opp_total_objects + PTRS_PER_PAGE - 1) /
PTRS_PER_PAGE;
end_nptr_page = (page_pool->opp_total_objects + nobjects +
PTRS_PER_PAGE - 1) / PTRS_PER_PAGE;
LASSERT(end_nptr_page <= page_pool->opp_max_ptr_pages);
np_idx = 0;
while (cur_nptr_page < end_nptr_page) {
LASSERT(page_pool->opp_ptr_pages[cur_nptr_page] == NULL);
LASSERT(np_idx < nptr_pages);
LASSERT(ptr_pages[np_idx] != NULL);
page_pool->opp_ptr_pages[cur_nptr_page++] = ptr_pages[np_idx];
ptr_pages[np_idx++] = NULL;
}
/*
* (3) free useless source ptr pages
*/
while (np_idx < nptr_pages) {
LASSERT(ptr_pages[np_idx] != NULL);
CDEBUG(D_SEC, "Free useless ptr pages: %i, %p\n", np_idx,
ptr_pages[np_idx]);
OBD_FREE(ptr_pages[np_idx], PAGE_SIZE);
ptr_pages[np_idx++] = NULL;
}
page_pool->opp_total_objects += nobjects;
page_pool->opp_free_objects += nobjects;
page_pool->opp_st_lowfree = page_pool->opp_free_objects;
if (page_pool->opp_total_objects > page_pool->opp_st_max_objects)
page_pool->opp_st_max_objects = page_pool->opp_total_objects;
CDEBUG(D_SEC, "add %d pages to total %lu\n", nobjects,
page_pool->opp_total_objects);
spin_unlock(&page_pool->opp_lock);
}
#define POOL_INIT_SIZE (PTLRPC_MAX_BRW_SIZE / 4)
static int pool_add_objects(int nobjects, struct obd_page_pool *page_pool)
{
unsigned int pool_order = page_pool->opp_order;
int nptr_pages, alloced = 0;
int i, j, rc = -ENOMEM;
unsigned long clean;
void ***ptr_pages;
if (nobjects < POOL_INIT_SIZE / object_size(page_pool))
nobjects = POOL_INIT_SIZE / object_size(page_pool);
mutex_lock(&page_pool->add_pages_mutex);
if (nobjects + page_pool->opp_total_objects >
page_pool->opp_max_objects) {
nobjects = page_pool->opp_max_objects -
page_pool->opp_total_objects;
}
LASSERT(nobjects > 0);
page_pool->opp_st_grows++;
nptr_pages = nobjects_to_nptr_pages(nobjects);
OBD_ALLOC_PTR_ARRAY(ptr_pages, nptr_pages);
if (ptr_pages == NULL)
goto out;
for (i = 0; i < nptr_pages; i++) {
OBD_ALLOC(ptr_pages[i], PAGE_SIZE);
if (ptr_pages[i] == NULL)
goto out_ptr_pages;
for (j = 0; j < PTRS_PER_PAGE && alloced < nobjects; j++) {
if (pool_order == 0)
ptr_pages[i][j] = alloc_page(GFP_NOFS |
__GFP_HIGHMEM);
else {
OBD_ALLOC_LARGE(ptr_pages[i][j],
object_size(page_pool));
/*
* It is possible that at some moment kmalloc
* will start to return non-page aligned memory,
* so leave this assort for quicker problem
* detection
*/
LASSERTF(IS_ALIGNED((unsigned long)
(ptr_pages[i][j]),
PAGE_SIZE),
"Page %p (order %i) is not aligned to PAGE_SIZE",
ptr_pages[i][j], page_pool->opp_order);
}
if (ptr_pages[i][j] == NULL)
goto out_ptr_pages;
alloced++;
}
}
LASSERT(alloced == nobjects);
pool_insert_ptrs(ptr_pages, nptr_pages, nobjects, page_pool);
CDEBUG(D_SEC, "added %d elements into pool:%d\n", nobjects, pool_order);
OBD_FREE_PTR_ARRAY(ptr_pages, nptr_pages);
rc = 0;
out_ptr_pages:
if (rc) {
clean = pool_cleanup(ptr_pages, nptr_pages, page_pool);
CDEBUG(D_SEC, "cleaned %lu elements from pool\n", clean);
OBD_FREE_PTR_ARRAY(ptr_pages, nptr_pages);
}
out:
if (rc) {
page_pool->opp_st_grow_fails++;
CERROR("Failed to allocate %d objects: rc = %d\n", nobjects,
rc);
}
mutex_unlock(&page_pool->add_pages_mutex);
return rc;
}
static inline void pool_wakeup(struct obd_page_pool *pool)
{
assert_spin_locked(&pool->opp_lock);
/* waitqueue_active */
if (unlikely(waitqueue_active(&pool->opp_waitq)))
wake_up_all(&pool->opp_waitq);
}
static int pool_should_grow(int needed, struct obd_page_pool *pool)
{
/*
* don't grow if someone else is growing the pool right now,
* or the pool has reached its full capacity
*/
if (pool->opp_growing ||
pool->opp_total_objects == pool->opp_max_objects)
return 0;
/* if total objects is not enough, we need to grow */
if (pool->opp_total_objects < needed)
return 1;
/*
* we wanted to return 0 here if there was a shrink just
* happened a moment ago, but this may cause deadlock if both
* client and ost live on single node.
*/
/*
* here we perhaps need consider other factors like wait queue
* length, idle index, etc. ?
*/
/* grow the pool in any other cases */
return 1;
}
/*
* Export the number of free objects in the pool of 'order'
*/
int obd_pool_get_free_objects(unsigned int order)
{
return page_pools[order]->opp_free_objects;
}
EXPORT_SYMBOL(obd_pool_get_free_objects);
/*
* Let outside world know if pool full capacity is reached
*/
int pool_is_at_full_capacity(int order)
{
return (page_pools[order]->opp_total_objects ==
page_pools[order]->opp_max_objects);
}
EXPORT_SYMBOL(pool_is_at_full_capacity);
static inline void **page_from_bulkdesc(void *array, int index)
{
struct ptlrpc_bulk_desc *desc = (struct ptlrpc_bulk_desc *)array;
return (void **)&desc->bd_enc_vec[index].bv_page;
}
static inline void **page_from_pagearray(void *array, int index)
{
struct page **pa = (struct page **)array;
return (void **)&pa[index];
}
static inline void **folio_from_folioarray(void *array, int index)
{
struct folio **pa = (struct folio **)array;
return (void **)&pa[index];
}
static inline void **object_from_bufarray(void *array, int index)
{
return (void **)array;
}
static bool __grow_pool_try(int needed, struct obd_page_pool *pool);
/*
* we allocate the requested objects atomically.
*/
static inline int __obd_pool_get_objects(void *array, unsigned int count,
unsigned int order,
void **(*object_from)(void *, int))
{
struct obd_page_pool *page_pool;
wait_queue_entry_t waitlink;
unsigned long this_idle = -1;
u64 tick_ns = 0;
int p_idx, g_idx;
int i, rc = 0;
if (order >= pools_count) {
CDEBUG(D_SEC,
"Requested pool order %d too big, max allocated order %d (chunk size %lu): %d\n",
order, pools_count - 1, PAGE_SIZE << (pools_count - 1),
rc);
return -EINVAL;
}
if (!array || count <= 0 || count > page_pools[order]->opp_max_objects)
return -EINVAL;
page_pool = page_pools[order];
spin_lock(&page_pool->opp_lock);
page_pool->opp_st_access++;
again:
if (unlikely(page_pool->opp_free_objects < count)) {
if (tick_ns == 0)
tick_ns = ktime_get_ns();
page_pool->opp_st_missings++;
page_pool->opp_pages_short += count;
/* if we aren't able to add objects, check if someone else is
* growing the pool and sleep if so, otherwise we return
* ENOMEM because we can't sleep here waiting for other ops to
* complete (main user is ptlrpcd, which must not sleep waiting
* for other ops... technically sleeping for pool growth is
* also questionable but it's very unlikely in practice to get
* stuck from this)
*
* if ENOMEM is returned here, the RPC will go back in the queue
*/
if (!__grow_pool_try(count, page_pool)) {
if (page_pool->opp_growing) {
if (++page_pool->opp_waitqlen >
page_pool->opp_st_max_wqlen)
page_pool->opp_st_max_wqlen =
page_pool->opp_waitqlen;
set_current_state(TASK_UNINTERRUPTIBLE);
init_wait(&waitlink);
add_wait_queue(&page_pool->opp_waitq,
&waitlink);
spin_unlock(&page_pool->opp_lock);
schedule();
remove_wait_queue(&page_pool->opp_waitq,
&waitlink);
spin_lock(&page_pool->opp_lock);
page_pool->opp_waitqlen--;
} else {
/*
* ptlrpcd thread should not sleep in that
* case or deadlock may occur!
* Instead, return -ENOMEM so that upper layers
* will put request back in queue.
*/
page_pool->opp_st_outofmem++;
GOTO(out_unlock, rc = -ENOMEM);
}
}
if (page_pool->opp_pages_short < count)
GOTO(out_unlock, rc = -EPROTO);
page_pool->opp_pages_short -= count;
this_idle = 0;
goto again;
}
/* record max wait time */
if (unlikely(tick_ns)) {
ktime_t tick = ktime_sub_ns(ktime_get(), tick_ns);
if (ktime_after(tick, page_pool->opp_st_max_wait))
page_pool->opp_st_max_wait = tick;
}
/* proceed with rest of allocation */
page_pool->opp_free_objects -= count;
p_idx = page_pool->opp_free_objects / PTRS_PER_PAGE;
g_idx = page_pool->opp_free_objects % PTRS_PER_PAGE;
for (i = 0; i < count; i++) {
void **objp = object_from(array, i);
if (page_pool->opp_ptr_pages[p_idx][g_idx] == NULL)
GOTO(out_unlock, rc = -EPROTO);
*objp = page_pool->opp_ptr_pages[p_idx][g_idx];
page_pool->opp_ptr_pages[p_idx][g_idx] = NULL;
if (++g_idx == PTRS_PER_PAGE) {
p_idx++;
g_idx = 0;
}
}
if (page_pool->opp_free_objects < page_pool->opp_st_lowfree)
page_pool->opp_st_lowfree =
page_pool->opp_free_objects;
/*
* new idle index = (old * weight + new) / (weight + 1)
*/
if (this_idle == -1) {
this_idle = page_pool->opp_free_objects * IDLE_IDX_MAX /
page_pool->opp_total_objects;
}
page_pool->opp_idle_idx = (page_pool->opp_idle_idx *
IDLE_IDX_WEIGHT + this_idle) /
(IDLE_IDX_WEIGHT + 1);
page_pool->opp_last_access = ktime_get_seconds();
out_unlock:
spin_unlock(&page_pool->opp_lock);
return rc;
}
int obd_pool_get_desc_pages(struct ptlrpc_bulk_desc *desc)
{
int rc;
LASSERT(desc->bd_iov_count > 0);
LASSERT(desc->bd_iov_count <= page_pools[0]->opp_max_objects);
/* resent bulk, enc iov might have been allocated previously */
if (desc->bd_enc_vec != NULL)
return 0;
OBD_ALLOC_LARGE(desc->bd_enc_vec,
desc->bd_iov_count * sizeof(*desc->bd_enc_vec));
if (desc->bd_enc_vec == NULL)
return -ENOMEM;
rc = __obd_pool_get_objects((void *)desc, desc->bd_iov_count, 0,
page_from_bulkdesc);
if (rc) {
OBD_FREE_LARGE(desc->bd_enc_vec,
desc->bd_iov_count *
sizeof(*desc->bd_enc_vec));
desc->bd_enc_vec = NULL;
}
return rc;
}
EXPORT_SYMBOL(obd_pool_get_desc_pages);
int obd_pool_get_pages_array(struct page **pa, unsigned int count)
{
return __obd_pool_get_objects((void *)pa, count, 0,
page_from_pagearray);
}
EXPORT_SYMBOL(obd_pool_get_pages_array);
int obd_pool_get_folios_array(struct folio **pa, unsigned int count)
{
return __obd_pool_get_objects((void *)pa, count, 0,
folio_from_folioarray);
}
EXPORT_SYMBOL(obd_pool_get_folios_array);
/* get 2^order pages region */
int obd_pool_get_objects(void **pages, unsigned int order)
{
return __obd_pool_get_objects((void *)pages, 1, order,
object_from_bufarray);
}
EXPORT_SYMBOL(obd_pool_get_objects);
static int __obd_pool_put_objects(void *array, unsigned int count,
unsigned int order,
void **(*object_from)(void *, int))
{
struct obd_page_pool *page_pool;
unsigned long this_idle;
int p_idx, g_idx;
int i, rc = 0;
LASSERTF(order < pools_count, "count %u, pool %u\n",
count, order);
if (!array) {
CERROR("Faled to put %u objects, from pool %u\n",
count, order);
return -EINVAL;
}
page_pool = page_pools[order];
LASSERTF(page_pool != NULL, "count %u, pool %u\n", count, order);
spin_lock(&page_pool->opp_lock);
p_idx = page_pool->opp_free_objects / PTRS_PER_PAGE;
g_idx = page_pool->opp_free_objects % PTRS_PER_PAGE;
if (page_pool->opp_free_objects + count > page_pool->opp_total_objects)
GOTO(out_unlock, rc = -EPROTO);
if (!page_pool->opp_ptr_pages[p_idx])
GOTO(out_unlock, rc = -EPROTO);
for (i = 0; i < count; i++) {
void **objp = object_from(array, i);
if (!*objp ||
page_pool->opp_ptr_pages[p_idx][g_idx] != NULL)
GOTO(out_unlock, rc = -EPROTO);
page_pool->opp_ptr_pages[p_idx][g_idx] = *objp;
if (++g_idx == PTRS_PER_PAGE) {
p_idx++;
g_idx = 0;
}
}
page_pool->opp_free_objects += count;
pool_wakeup(page_pool);
/*
* Recalculate opp_idle_idx in __sptlrpc_pool_put_pages the
* same way as it done in __sptlrpc_pool_get_pages. It is
* possible that opp_idle_idx might become 0 or very small
* (less than 10). If there is no new allocations it would
* be impossible to free anything from the pool during 40
* seconds(CACHE_QUIESCENT_PERIOD) despite the large amount
* of free elements ready to shrinking.
*/
this_idle = page_pool->opp_free_objects * IDLE_IDX_MAX /
page_pool->opp_total_objects;
page_pool->opp_idle_idx = (page_pool->opp_idle_idx *
IDLE_IDX_WEIGHT + this_idle) /
(IDLE_IDX_WEIGHT + 1);
out_unlock:
spin_unlock(&page_pool->opp_lock);
return rc;
}
void obd_pool_put_desc_pages(struct ptlrpc_bulk_desc *desc)
{
int rc;
if (desc->bd_enc_vec == NULL)
return;
rc = __obd_pool_put_objects((void *)desc, desc->bd_iov_count, 0,
page_from_bulkdesc);
if (rc)
CDEBUG(D_SEC, "error putting pages in pool: %d\n", rc);
OBD_FREE_LARGE(desc->bd_enc_vec,
desc->bd_iov_count * sizeof(*desc->bd_enc_vec));
desc->bd_enc_vec = NULL;
}
EXPORT_SYMBOL(obd_pool_put_desc_pages);
void obd_pool_put_pages_array(struct page **pa, unsigned int count)
{
int rc;
rc = __obd_pool_put_objects((void *)pa, count, 0, page_from_pagearray);
if (rc)
CDEBUG(D_SEC, "error putting pages in pool: %d\n", rc);
}
EXPORT_SYMBOL(obd_pool_put_pages_array);
void obd_pool_put_folios_array(struct folio **pa, unsigned int count)
{
int rc;
rc = __obd_pool_put_objects((void *)pa, count, 0,
folio_from_folioarray);
if (rc)
CDEBUG(D_SEC, "error putting pages in pool: %d\n", rc);
}
EXPORT_SYMBOL(obd_pool_put_folios_array);
/* put 2^order pages region */
void obd_pool_put_objects(void *buf, unsigned int order)
{
int rc;
rc = __obd_pool_put_objects(buf, 1, order, object_from_bufarray);
if (rc)
CDEBUG(D_SEC, "error putting objects in pool: %d\n", rc);
}
EXPORT_SYMBOL(obd_pool_put_objects);
/* called with pool->opp_lock held */
static bool __grow_pool_try(int needed, struct obd_page_pool *pool)
{
bool pool_grown = false;
assert_spin_locked(&pool->opp_lock);
if (pool_should_grow(needed, pool)) {
unsigned int to_add;
int rc;
pool->opp_growing = 1;
/* the pool of single pages is grown a large amount on
* first use
*/
if (pool->opp_order == 0 &&
pool->opp_total_objects == 0)
to_add = PTLRPC_MAX_BRW_PAGES * 2;
else /* otherwise, we add requested or at least 8 items */
to_add = max(needed, 8);
spin_unlock(&pool->opp_lock);
CDEBUG(D_SEC,
"pool %d is %lu elements (size %d bytes), growing by %d items\n",
pool->opp_order, pool->opp_pages_short,
object_size(pool), to_add);
/* we can't hold a spinlock over page allocation */
rc = pool_add_objects(to_add, pool);
if (rc == 0)
pool_grown = true;
spin_lock(&pool->opp_lock);
pool->opp_growing = 0;
pool_wakeup(pool);
}
return pool_grown;
}
static bool grow_pool_try(int needed, struct obd_page_pool *pool)
{
bool rc;
spin_lock(&pool->opp_lock);
rc = __grow_pool_try(needed, pool);
spin_unlock(&pool->opp_lock);
return rc;
}
/*
* we don't do much stuff for add_user/del_user anymore, except adding some
* initial pages in add_user() if current pool is empty, rest would be
* handled by the pool self-adaption.
*/
void obd_pool_add_user(void)
{
struct obd_page_pool *pool = page_pools[0];
/* since this is startup, no one is waiting for these pages, so we
* don't worry about sucess or failure here
*/
grow_pool_try(1, pool);
}
EXPORT_SYMBOL(obd_pool_add_user);
static inline void pool_ptrs_alloc(struct obd_page_pool *pool)
{
LASSERT(pool->opp_max_ptr_pages);
OBD_ALLOC_LARGE(pool->opp_ptr_pages,
pool->opp_max_ptr_pages *
sizeof(*pool->opp_ptr_pages));
}
static inline void pool_ptrs_free(struct obd_page_pool *pool)
{
LASSERT(pool->opp_max_ptr_pages);
LASSERT(pool->opp_ptr_pages);
OBD_FREE_LARGE(pool->opp_ptr_pages,
pool->opp_max_ptr_pages * sizeof(*pool->opp_ptr_pages));
}
int obd_pool_init(void)
{
struct obd_page_pool *pool;
int pool_max_pages = compat_totalram_pages() / POOLS_COUNT;
struct dentry *parent;
int pool_order = 0;
int to_revert;
int rc = 0;
ENTRY;
if (pool_max_memory_mb == 0 && enc_pool_max_memory_mb > 0)
pool_max_memory_mb = enc_pool_max_memory_mb;
if (pool_max_memory_mb > 0 &&
pool_max_memory_mb <= PAGES_TO_MiB(compat_totalram_pages()))
pool_max_pages = MiB_TO_PAGES(pool_max_memory_mb);
OBD_ALLOC(page_pools, POOLS_COUNT * sizeof(*page_pools));
if (page_pools == NULL)
RETURN(-ENOMEM);
OBD_ALLOC(pool_shrinkers, POOLS_COUNT * sizeof(*pool_shrinkers));
if (pool_shrinkers == NULL)
GOTO(fail2, rc = -ENOMEM);
parent = debugfs_create_dir("page_pools", debugfs_lustre_root);
for (pool_order = 0; pool_order < POOLS_COUNT; pool_order++) {
OBD_ALLOC(page_pools[pool_order], sizeof(**page_pools));
if (page_pools[pool_order] == NULL)
GOTO(fail, rc = -ENOMEM);
pool = page_pools[pool_order];
pool->opp_max_objects = pool_max_pages >> pool_order;
pool->opp_max_ptr_pages =
nobjects_to_nptr_pages(pool->opp_max_objects);
/* If opp_max_ptr_pages is 0, it means there is not enough
* memory on the node to allocate all the pools. So stop the
* loop here to avoid having empty pools.
*/
if (!pool->opp_max_ptr_pages) {
CWARN("Cannot allocate pool %i, not enough memory. Max available compression chunk is %lu.\n",
pool_order,
pool_order ? PAGE_SIZE << (pool_order - 1) : 0);
pools_count = pool_order;
OBD_FREE(pool, sizeof(**page_pools));
break;
}
init_waitqueue_head(&pool->opp_waitq);
pool->opp_last_shrink = ktime_get_seconds();
pool->opp_last_access = ktime_get_seconds();
spin_lock_init(&pool->opp_lock);
pool->opp_st_max_wait = ktime_set(0, 0);
pool_ptrs_alloc(pool);
pool->opp_order = pool_order;
CDEBUG(D_SEC, "Allocated pool %i\n", pool_order);
if (pool->opp_ptr_pages == NULL)
GOTO(fail, rc = -ENOMEM);
/* Pass pool number as part of pool_shrinker_seeks value */
pool->pool_shrinker = ll_shrinker_alloc(0, "obd_pool");
if (IS_ERR(pool->pool_shrinker))
GOTO(fail, rc = PTR_ERR(pool->pool_shrinker));
pool->pool_shrinker->count_objects = pool_shrink_count;
pool->pool_shrinker->scan_objects = pool_shrink_scan;
ll_shrinker_register(pool->pool_shrinker);
if (parent) {
char path[MAX_OBD_NAME];
scnprintf(path, sizeof(path), "obd_pool-%d",
pool_order);
ldebugfs_add_symlink(path, parent->d_name.name,
"../../shrinker/%s",
shrinker_debugfs_path(pool->pool_shrinker));
}
pool_shrinkers[pool_order] = pool->pool_shrinker;
mutex_init(&pool->add_pages_mutex);
}
RETURN(0);
fail:
to_revert = pool_order;
for (pool_order = 0; pool_order <= to_revert; pool_order++) {
pool = page_pools[pool_order];
if (pool) {
if (pool->opp_ptr_pages)
pool_ptrs_free(pool);
OBD_FREE(pool, sizeof(**page_pools));
}
}
OBD_FREE(pool_shrinkers, POOLS_COUNT * sizeof(*pool_shrinkers));
fail2:
OBD_FREE(page_pools, POOLS_COUNT * sizeof(*page_pools));
RETURN(rc);
}
EXPORT_SYMBOL(obd_pool_init);
void obd_pool_fini(void)
{
unsigned long cleaned, nptr_pages;
int pool_order;
struct obd_page_pool *pool;
for (pool_order = 0; pool_order < pools_count; pool_order++) {
pool = page_pools[pool_order];
ll_shrinker_free(pool->pool_shrinker);
LASSERT(pool->opp_ptr_pages);
LASSERT(pool->opp_total_objects == pool->opp_free_objects);
nptr_pages = nobjects_to_nptr_pages(pool->opp_total_objects);
cleaned = pool_cleanup(pool->opp_ptr_pages, nptr_pages, pool);
LASSERT(cleaned == pool->opp_total_objects);
pool_ptrs_free(pool);
if (pool->opp_st_access > 0) {
CDEBUG(D_SEC,
"max objects %lu, grows %u, grow fails %u, shrinks %u, access %lu, missing %lu, max qlen %u, max wait ms %lld, out of mem %lu\n",
pool->opp_st_max_objects,
pool->opp_st_grows,
pool->opp_st_grow_fails,
pool->opp_st_shrinks,
pool->opp_st_access,
pool->opp_st_missings,
pool->opp_st_max_wqlen,
ktime_to_ms(pool->opp_st_max_wait),
pool->opp_st_outofmem);
}
OBD_FREE(pool, sizeof(**page_pools));
}
OBD_FREE(page_pools, POOLS_COUNT * sizeof(*page_pools));
OBD_FREE(pool_shrinkers, POOLS_COUNT * sizeof(*pool_shrinkers));
}
EXPORT_SYMBOL(obd_pool_fini);