zsmalloc: support compaction
This patch provides core functions for migration of zsmalloc. Migraion policy is simple as follows. for each size class { while { src_page = get zs_page from ZS_ALMOST_EMPTY if (!src_page) break; dst_page = get zs_page from ZS_ALMOST_FULL if (!dst_page) dst_page = get zs_page from ZS_ALMOST_EMPTY if (!dst_page) break; migrate(from src_page, to dst_page); } } For migration, we need to identify which objects in zspage are allocated to migrate them out. We could know it by iterating of freed objects in a zspage because first_page of zspage keeps free objects singly-linked list but it's not efficient. Instead, this patch adds a tag(ie, OBJ_ALLOCATED_TAG) in header of each object(ie, handle) so we could check whether the object is allocated easily. This patch adds another status bit in handle to synchronize between user access through zs_map_object and migration. During migration, we cannot move objects user are using due to data coherency between old object and new object. [akpm@linux-foundation.org: zsmalloc.c needs sched.h for cond_resched()] Signed-off-by: Minchan Kim <minchan@kernel.org> Cc: Juneho Choi <juno.choi@lge.com> Cc: Gunho Lee <gunho.lee@lge.com> Cc: Luigi Semenzato <semenzato@google.com> Cc: Dan Streetman <ddstreet@ieee.org> Cc: Seth Jennings <sjennings@variantweb.net> Cc: Nitin Gupta <ngupta@vflare.org> Cc: Jerome Marchand <jmarchan@redhat.com> Cc: Sergey Senozhatsky <sergey.senozhatsky@gmail.com> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Cc: Mel Gorman <mel@csn.ul.ie> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
c78062612f
commit
312fcae227
@ -47,5 +47,6 @@ void *zs_map_object(struct zs_pool *pool, unsigned long handle,
|
|||||||
void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
|
void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
|
||||||
|
|
||||||
unsigned long zs_get_total_pages(struct zs_pool *pool);
|
unsigned long zs_get_total_pages(struct zs_pool *pool);
|
||||||
|
unsigned long zs_compact(struct zs_pool *pool);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
382
mm/zsmalloc.c
382
mm/zsmalloc.c
@ -78,6 +78,7 @@
|
|||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/sched.h>
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
#include <linux/highmem.h>
|
#include <linux/highmem.h>
|
||||||
@ -135,7 +136,26 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#define _PFN_BITS (MAX_PHYSMEM_BITS - PAGE_SHIFT)
|
#define _PFN_BITS (MAX_PHYSMEM_BITS - PAGE_SHIFT)
|
||||||
#define OBJ_INDEX_BITS (BITS_PER_LONG - _PFN_BITS)
|
|
||||||
|
/*
|
||||||
|
* Memory for allocating for handle keeps object position by
|
||||||
|
* encoding <page, obj_idx> and the encoded value has a room
|
||||||
|
* in least bit(ie, look at obj_to_location).
|
||||||
|
* We use the bit to synchronize between object access by
|
||||||
|
* user and migration.
|
||||||
|
*/
|
||||||
|
#define HANDLE_PIN_BIT 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Head in allocated object should have OBJ_ALLOCATED_TAG
|
||||||
|
* to identify the object was allocated or not.
|
||||||
|
* It's okay to add the status bit in the least bit because
|
||||||
|
* header keeps handle which is 4byte-aligned address so we
|
||||||
|
* have room for two bit at least.
|
||||||
|
*/
|
||||||
|
#define OBJ_ALLOCATED_TAG 1
|
||||||
|
#define OBJ_TAG_BITS 1
|
||||||
|
#define OBJ_INDEX_BITS (BITS_PER_LONG - _PFN_BITS - OBJ_TAG_BITS)
|
||||||
#define OBJ_INDEX_MASK ((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
|
#define OBJ_INDEX_MASK ((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
|
||||||
|
|
||||||
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
|
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
|
||||||
@ -610,35 +630,35 @@ static struct page *get_next_page(struct page *page)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Encode <page, obj_idx> as a single handle value.
|
* Encode <page, obj_idx> as a single handle value.
|
||||||
* On hardware platforms with physical memory starting at 0x0 the pfn
|
* We use the least bit of handle for tagging.
|
||||||
* could be 0 so we ensure that the handle will never be 0 by adjusting the
|
|
||||||
* encoded obj_idx value before encoding.
|
|
||||||
*/
|
*/
|
||||||
static void *obj_location_to_handle(struct page *page, unsigned long obj_idx)
|
static void *location_to_obj(struct page *page, unsigned long obj_idx)
|
||||||
{
|
{
|
||||||
unsigned long handle;
|
unsigned long obj;
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
BUG_ON(obj_idx);
|
BUG_ON(obj_idx);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle = page_to_pfn(page) << OBJ_INDEX_BITS;
|
obj = page_to_pfn(page) << OBJ_INDEX_BITS;
|
||||||
handle |= ((obj_idx + 1) & OBJ_INDEX_MASK);
|
obj |= ((obj_idx) & OBJ_INDEX_MASK);
|
||||||
|
obj <<= OBJ_TAG_BITS;
|
||||||
|
|
||||||
return (void *)handle;
|
return (void *)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Decode <page, obj_idx> pair from the given object handle. We adjust the
|
* Decode <page, obj_idx> pair from the given object handle. We adjust the
|
||||||
* decoded obj_idx back to its original value since it was adjusted in
|
* decoded obj_idx back to its original value since it was adjusted in
|
||||||
* obj_location_to_handle().
|
* location_to_obj().
|
||||||
*/
|
*/
|
||||||
static void obj_to_location(unsigned long handle, struct page **page,
|
static void obj_to_location(unsigned long obj, struct page **page,
|
||||||
unsigned long *obj_idx)
|
unsigned long *obj_idx)
|
||||||
{
|
{
|
||||||
*page = pfn_to_page(handle >> OBJ_INDEX_BITS);
|
obj >>= OBJ_TAG_BITS;
|
||||||
*obj_idx = (handle & OBJ_INDEX_MASK) - 1;
|
*page = pfn_to_page(obj >> OBJ_INDEX_BITS);
|
||||||
|
*obj_idx = (obj & OBJ_INDEX_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned long handle_to_obj(unsigned long handle)
|
static unsigned long handle_to_obj(unsigned long handle)
|
||||||
@ -646,6 +666,11 @@ static unsigned long handle_to_obj(unsigned long handle)
|
|||||||
return *(unsigned long *)handle;
|
return *(unsigned long *)handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned long obj_to_head(void *obj)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)obj;
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned long obj_idx_to_offset(struct page *page,
|
static unsigned long obj_idx_to_offset(struct page *page,
|
||||||
unsigned long obj_idx, int class_size)
|
unsigned long obj_idx, int class_size)
|
||||||
{
|
{
|
||||||
@ -657,6 +682,25 @@ static unsigned long obj_idx_to_offset(struct page *page,
|
|||||||
return off + obj_idx * class_size;
|
return off + obj_idx * class_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int trypin_tag(unsigned long handle)
|
||||||
|
{
|
||||||
|
unsigned long *ptr = (unsigned long *)handle;
|
||||||
|
|
||||||
|
return !test_and_set_bit_lock(HANDLE_PIN_BIT, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pin_tag(unsigned long handle)
|
||||||
|
{
|
||||||
|
while (!trypin_tag(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unpin_tag(unsigned long handle)
|
||||||
|
{
|
||||||
|
unsigned long *ptr = (unsigned long *)handle;
|
||||||
|
|
||||||
|
clear_bit_unlock(HANDLE_PIN_BIT, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
static void reset_page(struct page *page)
|
static void reset_page(struct page *page)
|
||||||
{
|
{
|
||||||
clear_bit(PG_private, &page->flags);
|
clear_bit(PG_private, &page->flags);
|
||||||
@ -718,7 +762,7 @@ static void init_zspage(struct page *first_page, struct size_class *class)
|
|||||||
link = (struct link_free *)vaddr + off / sizeof(*link);
|
link = (struct link_free *)vaddr + off / sizeof(*link);
|
||||||
|
|
||||||
while ((off += class->size) < PAGE_SIZE) {
|
while ((off += class->size) < PAGE_SIZE) {
|
||||||
link->next = obj_location_to_handle(page, i++);
|
link->next = location_to_obj(page, i++);
|
||||||
link += class->size / sizeof(*link);
|
link += class->size / sizeof(*link);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -728,7 +772,7 @@ static void init_zspage(struct page *first_page, struct size_class *class)
|
|||||||
* page (if present)
|
* page (if present)
|
||||||
*/
|
*/
|
||||||
next_page = get_next_page(page);
|
next_page = get_next_page(page);
|
||||||
link->next = obj_location_to_handle(next_page, 0);
|
link->next = location_to_obj(next_page, 0);
|
||||||
kunmap_atomic(vaddr);
|
kunmap_atomic(vaddr);
|
||||||
page = next_page;
|
page = next_page;
|
||||||
off %= PAGE_SIZE;
|
off %= PAGE_SIZE;
|
||||||
@ -782,7 +826,7 @@ static struct page *alloc_zspage(struct size_class *class, gfp_t flags)
|
|||||||
|
|
||||||
init_zspage(first_page, class);
|
init_zspage(first_page, class);
|
||||||
|
|
||||||
first_page->freelist = obj_location_to_handle(first_page, 0);
|
first_page->freelist = location_to_obj(first_page, 0);
|
||||||
/* Maximum number of objects we can store in this zspage */
|
/* Maximum number of objects we can store in this zspage */
|
||||||
first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
|
first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
|
||||||
|
|
||||||
@ -1017,6 +1061,13 @@ static bool can_merge(struct size_class *prev, int size, int pages_per_zspage)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool zspage_full(struct page *page)
|
||||||
|
{
|
||||||
|
BUG_ON(!is_first_page(page));
|
||||||
|
|
||||||
|
return page->inuse == page->objects;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_ZSMALLOC_STAT
|
#ifdef CONFIG_ZSMALLOC_STAT
|
||||||
|
|
||||||
static inline void zs_stat_inc(struct size_class *class,
|
static inline void zs_stat_inc(struct size_class *class,
|
||||||
@ -1219,6 +1270,9 @@ void *zs_map_object(struct zs_pool *pool, unsigned long handle,
|
|||||||
*/
|
*/
|
||||||
BUG_ON(in_interrupt());
|
BUG_ON(in_interrupt());
|
||||||
|
|
||||||
|
/* From now on, migration cannot move the object */
|
||||||
|
pin_tag(handle);
|
||||||
|
|
||||||
obj = handle_to_obj(handle);
|
obj = handle_to_obj(handle);
|
||||||
obj_to_location(obj, &page, &obj_idx);
|
obj_to_location(obj, &page, &obj_idx);
|
||||||
get_zspage_mapping(get_first_page(page), &class_idx, &fg);
|
get_zspage_mapping(get_first_page(page), &class_idx, &fg);
|
||||||
@ -1276,6 +1330,7 @@ void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
|
|||||||
__zs_unmap_object(area, pages, off, class->size);
|
__zs_unmap_object(area, pages, off, class->size);
|
||||||
}
|
}
|
||||||
put_cpu_var(zs_map_area);
|
put_cpu_var(zs_map_area);
|
||||||
|
unpin_tag(handle);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(zs_unmap_object);
|
EXPORT_SYMBOL_GPL(zs_unmap_object);
|
||||||
|
|
||||||
@ -1289,6 +1344,7 @@ static unsigned long obj_malloc(struct page *first_page,
|
|||||||
unsigned long m_objidx, m_offset;
|
unsigned long m_objidx, m_offset;
|
||||||
void *vaddr;
|
void *vaddr;
|
||||||
|
|
||||||
|
handle |= OBJ_ALLOCATED_TAG;
|
||||||
obj = (unsigned long)first_page->freelist;
|
obj = (unsigned long)first_page->freelist;
|
||||||
obj_to_location(obj, &m_page, &m_objidx);
|
obj_to_location(obj, &m_page, &m_objidx);
|
||||||
m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
|
m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
|
||||||
@ -1374,6 +1430,7 @@ static void obj_free(struct zs_pool *pool, struct size_class *class,
|
|||||||
|
|
||||||
BUG_ON(!obj);
|
BUG_ON(!obj);
|
||||||
|
|
||||||
|
obj &= ~OBJ_ALLOCATED_TAG;
|
||||||
obj_to_location(obj, &f_page, &f_objidx);
|
obj_to_location(obj, &f_page, &f_objidx);
|
||||||
first_page = get_first_page(f_page);
|
first_page = get_first_page(f_page);
|
||||||
|
|
||||||
@ -1402,8 +1459,8 @@ void zs_free(struct zs_pool *pool, unsigned long handle)
|
|||||||
if (unlikely(!handle))
|
if (unlikely(!handle))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
pin_tag(handle);
|
||||||
obj = handle_to_obj(handle);
|
obj = handle_to_obj(handle);
|
||||||
free_handle(pool, handle);
|
|
||||||
obj_to_location(obj, &f_page, &f_objidx);
|
obj_to_location(obj, &f_page, &f_objidx);
|
||||||
first_page = get_first_page(f_page);
|
first_page = get_first_page(f_page);
|
||||||
|
|
||||||
@ -1413,19 +1470,302 @@ void zs_free(struct zs_pool *pool, unsigned long handle)
|
|||||||
spin_lock(&class->lock);
|
spin_lock(&class->lock);
|
||||||
obj_free(pool, class, obj);
|
obj_free(pool, class, obj);
|
||||||
fullness = fix_fullness_group(class, first_page);
|
fullness = fix_fullness_group(class, first_page);
|
||||||
if (fullness == ZS_EMPTY)
|
if (fullness == ZS_EMPTY) {
|
||||||
zs_stat_dec(class, OBJ_ALLOCATED, get_maxobj_per_zspage(
|
zs_stat_dec(class, OBJ_ALLOCATED, get_maxobj_per_zspage(
|
||||||
class->size, class->pages_per_zspage));
|
class->size, class->pages_per_zspage));
|
||||||
spin_unlock(&class->lock);
|
|
||||||
|
|
||||||
if (fullness == ZS_EMPTY) {
|
|
||||||
atomic_long_sub(class->pages_per_zspage,
|
atomic_long_sub(class->pages_per_zspage,
|
||||||
&pool->pages_allocated);
|
&pool->pages_allocated);
|
||||||
free_zspage(first_page);
|
free_zspage(first_page);
|
||||||
}
|
}
|
||||||
|
spin_unlock(&class->lock);
|
||||||
|
unpin_tag(handle);
|
||||||
|
|
||||||
|
free_handle(pool, handle);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(zs_free);
|
EXPORT_SYMBOL_GPL(zs_free);
|
||||||
|
|
||||||
|
static void zs_object_copy(unsigned long src, unsigned long dst,
|
||||||
|
struct size_class *class)
|
||||||
|
{
|
||||||
|
struct page *s_page, *d_page;
|
||||||
|
unsigned long s_objidx, d_objidx;
|
||||||
|
unsigned long s_off, d_off;
|
||||||
|
void *s_addr, *d_addr;
|
||||||
|
int s_size, d_size, size;
|
||||||
|
int written = 0;
|
||||||
|
|
||||||
|
s_size = d_size = class->size;
|
||||||
|
|
||||||
|
obj_to_location(src, &s_page, &s_objidx);
|
||||||
|
obj_to_location(dst, &d_page, &d_objidx);
|
||||||
|
|
||||||
|
s_off = obj_idx_to_offset(s_page, s_objidx, class->size);
|
||||||
|
d_off = obj_idx_to_offset(d_page, d_objidx, class->size);
|
||||||
|
|
||||||
|
if (s_off + class->size > PAGE_SIZE)
|
||||||
|
s_size = PAGE_SIZE - s_off;
|
||||||
|
|
||||||
|
if (d_off + class->size > PAGE_SIZE)
|
||||||
|
d_size = PAGE_SIZE - d_off;
|
||||||
|
|
||||||
|
s_addr = kmap_atomic(s_page);
|
||||||
|
d_addr = kmap_atomic(d_page);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
size = min(s_size, d_size);
|
||||||
|
memcpy(d_addr + d_off, s_addr + s_off, size);
|
||||||
|
written += size;
|
||||||
|
|
||||||
|
if (written == class->size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (s_off + size >= PAGE_SIZE) {
|
||||||
|
kunmap_atomic(d_addr);
|
||||||
|
kunmap_atomic(s_addr);
|
||||||
|
s_page = get_next_page(s_page);
|
||||||
|
BUG_ON(!s_page);
|
||||||
|
s_addr = kmap_atomic(s_page);
|
||||||
|
d_addr = kmap_atomic(d_page);
|
||||||
|
s_size = class->size - written;
|
||||||
|
s_off = 0;
|
||||||
|
} else {
|
||||||
|
s_off += size;
|
||||||
|
s_size -= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d_off + size >= PAGE_SIZE) {
|
||||||
|
kunmap_atomic(d_addr);
|
||||||
|
d_page = get_next_page(d_page);
|
||||||
|
BUG_ON(!d_page);
|
||||||
|
d_addr = kmap_atomic(d_page);
|
||||||
|
d_size = class->size - written;
|
||||||
|
d_off = 0;
|
||||||
|
} else {
|
||||||
|
d_off += size;
|
||||||
|
d_size -= size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kunmap_atomic(d_addr);
|
||||||
|
kunmap_atomic(s_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find alloced object in zspage from index object and
|
||||||
|
* return handle.
|
||||||
|
*/
|
||||||
|
static unsigned long find_alloced_obj(struct page *page, int index,
|
||||||
|
struct size_class *class)
|
||||||
|
{
|
||||||
|
unsigned long head;
|
||||||
|
int offset = 0;
|
||||||
|
unsigned long handle = 0;
|
||||||
|
void *addr = kmap_atomic(page);
|
||||||
|
|
||||||
|
if (!is_first_page(page))
|
||||||
|
offset = page->index;
|
||||||
|
offset += class->size * index;
|
||||||
|
|
||||||
|
while (offset < PAGE_SIZE) {
|
||||||
|
head = obj_to_head(addr + offset);
|
||||||
|
if (head & OBJ_ALLOCATED_TAG) {
|
||||||
|
handle = head & ~OBJ_ALLOCATED_TAG;
|
||||||
|
if (trypin_tag(handle))
|
||||||
|
break;
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += class->size;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
kunmap_atomic(addr);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct zs_compact_control {
|
||||||
|
/* Source page for migration which could be a subpage of zspage. */
|
||||||
|
struct page *s_page;
|
||||||
|
/* Destination page for migration which should be a first page
|
||||||
|
* of zspage. */
|
||||||
|
struct page *d_page;
|
||||||
|
/* Starting object index within @s_page which used for live object
|
||||||
|
* in the subpage. */
|
||||||
|
int index;
|
||||||
|
/* how many of objects are migrated */
|
||||||
|
int nr_migrated;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int migrate_zspage(struct zs_pool *pool, struct size_class *class,
|
||||||
|
struct zs_compact_control *cc)
|
||||||
|
{
|
||||||
|
unsigned long used_obj, free_obj;
|
||||||
|
unsigned long handle;
|
||||||
|
struct page *s_page = cc->s_page;
|
||||||
|
struct page *d_page = cc->d_page;
|
||||||
|
unsigned long index = cc->index;
|
||||||
|
int nr_migrated = 0;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
handle = find_alloced_obj(s_page, index, class);
|
||||||
|
if (!handle) {
|
||||||
|
s_page = get_next_page(s_page);
|
||||||
|
if (!s_page)
|
||||||
|
break;
|
||||||
|
index = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop if there is no more space */
|
||||||
|
if (zspage_full(d_page)) {
|
||||||
|
unpin_tag(handle);
|
||||||
|
ret = -ENOMEM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
used_obj = handle_to_obj(handle);
|
||||||
|
free_obj = obj_malloc(d_page, class, handle);
|
||||||
|
zs_object_copy(used_obj, free_obj, class);
|
||||||
|
index++;
|
||||||
|
record_obj(handle, free_obj);
|
||||||
|
unpin_tag(handle);
|
||||||
|
obj_free(pool, class, used_obj);
|
||||||
|
nr_migrated++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remember last position in this iteration */
|
||||||
|
cc->s_page = s_page;
|
||||||
|
cc->index = index;
|
||||||
|
cc->nr_migrated = nr_migrated;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct page *alloc_target_page(struct size_class *class)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct page *page;
|
||||||
|
|
||||||
|
for (i = 0; i < _ZS_NR_FULLNESS_GROUPS; i++) {
|
||||||
|
page = class->fullness_list[i];
|
||||||
|
if (page) {
|
||||||
|
remove_zspage(page, class, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void putback_zspage(struct zs_pool *pool, struct size_class *class,
|
||||||
|
struct page *first_page)
|
||||||
|
{
|
||||||
|
int class_idx;
|
||||||
|
enum fullness_group fullness;
|
||||||
|
|
||||||
|
BUG_ON(!is_first_page(first_page));
|
||||||
|
|
||||||
|
get_zspage_mapping(first_page, &class_idx, &fullness);
|
||||||
|
insert_zspage(first_page, class, fullness);
|
||||||
|
fullness = fix_fullness_group(class, first_page);
|
||||||
|
if (fullness == ZS_EMPTY) {
|
||||||
|
zs_stat_dec(class, OBJ_ALLOCATED, get_maxobj_per_zspage(
|
||||||
|
class->size, class->pages_per_zspage));
|
||||||
|
atomic_long_sub(class->pages_per_zspage,
|
||||||
|
&pool->pages_allocated);
|
||||||
|
|
||||||
|
free_zspage(first_page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct page *isolate_source_page(struct size_class *class)
|
||||||
|
{
|
||||||
|
struct page *page;
|
||||||
|
|
||||||
|
page = class->fullness_list[ZS_ALMOST_EMPTY];
|
||||||
|
if (page)
|
||||||
|
remove_zspage(page, class, ZS_ALMOST_EMPTY);
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long __zs_compact(struct zs_pool *pool,
|
||||||
|
struct size_class *class)
|
||||||
|
{
|
||||||
|
int nr_to_migrate;
|
||||||
|
struct zs_compact_control cc;
|
||||||
|
struct page *src_page;
|
||||||
|
struct page *dst_page = NULL;
|
||||||
|
unsigned long nr_total_migrated = 0;
|
||||||
|
|
||||||
|
cond_resched();
|
||||||
|
|
||||||
|
spin_lock(&class->lock);
|
||||||
|
while ((src_page = isolate_source_page(class))) {
|
||||||
|
|
||||||
|
BUG_ON(!is_first_page(src_page));
|
||||||
|
|
||||||
|
/* The goal is to migrate all live objects in source page */
|
||||||
|
nr_to_migrate = src_page->inuse;
|
||||||
|
cc.index = 0;
|
||||||
|
cc.s_page = src_page;
|
||||||
|
|
||||||
|
while ((dst_page = alloc_target_page(class))) {
|
||||||
|
cc.d_page = dst_page;
|
||||||
|
/*
|
||||||
|
* If there is no more space in dst_page, try to
|
||||||
|
* allocate another zspage.
|
||||||
|
*/
|
||||||
|
if (!migrate_zspage(pool, class, &cc))
|
||||||
|
break;
|
||||||
|
|
||||||
|
putback_zspage(pool, class, dst_page);
|
||||||
|
nr_total_migrated += cc.nr_migrated;
|
||||||
|
nr_to_migrate -= cc.nr_migrated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop if we couldn't find slot */
|
||||||
|
if (dst_page == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
putback_zspage(pool, class, dst_page);
|
||||||
|
putback_zspage(pool, class, src_page);
|
||||||
|
spin_unlock(&class->lock);
|
||||||
|
nr_total_migrated += cc.nr_migrated;
|
||||||
|
cond_resched();
|
||||||
|
spin_lock(&class->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src_page)
|
||||||
|
putback_zspage(pool, class, src_page);
|
||||||
|
|
||||||
|
spin_unlock(&class->lock);
|
||||||
|
|
||||||
|
return nr_total_migrated;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long zs_compact(struct zs_pool *pool)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
unsigned long nr_migrated = 0;
|
||||||
|
struct size_class *class;
|
||||||
|
|
||||||
|
for (i = zs_size_classes - 1; i >= 0; i--) {
|
||||||
|
class = pool->size_class[i];
|
||||||
|
if (!class)
|
||||||
|
continue;
|
||||||
|
if (class->index != i)
|
||||||
|
continue;
|
||||||
|
nr_migrated += __zs_compact(pool, class);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronize_rcu();
|
||||||
|
|
||||||
|
return nr_migrated;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(zs_compact);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* zs_create_pool - Creates an allocation pool to work from.
|
* zs_create_pool - Creates an allocation pool to work from.
|
||||||
* @flags: allocation flags used to allocate pool metadata
|
* @flags: allocation flags used to allocate pool metadata
|
||||||
|
Loading…
Reference in New Issue
Block a user