2019-06-01 08:08:42 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2010-04-09 09:57:01 +00:00
|
|
|
/*
|
|
|
|
* mm/percpu-vm.c - vmalloc area based chunk allocation
|
|
|
|
*
|
|
|
|
* Copyright (C) 2010 SUSE Linux Products GmbH
|
|
|
|
* Copyright (C) 2010 Tejun Heo <tj@kernel.org>
|
|
|
|
*
|
|
|
|
* Chunks are mapped into vmalloc areas and populated page by page.
|
|
|
|
* This is the default chunk allocator.
|
|
|
|
*/
|
2021-04-30 05:58:53 +00:00
|
|
|
#include "internal.h"
|
2010-04-09 09:57:01 +00:00
|
|
|
|
|
|
|
static struct page *pcpu_chunk_page(struct pcpu_chunk *chunk,
|
|
|
|
unsigned int cpu, int page_idx)
|
|
|
|
{
|
|
|
|
/* must not be used on pre-mapped chunk */
|
|
|
|
WARN_ON(chunk->immutable);
|
|
|
|
|
|
|
|
return vmalloc_to_page((void *)pcpu_chunk_addr(chunk, cpu, page_idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-09-02 18:46:01 +00:00
|
|
|
* pcpu_get_pages - get temp pages array
|
2010-04-09 09:57:01 +00:00
|
|
|
*
|
2014-09-02 18:46:01 +00:00
|
|
|
* Returns pointer to array of pointers to struct page which can be indexed
|
2014-09-02 18:46:01 +00:00
|
|
|
* with pcpu_page_idx(). Note that there is only one array and accesses
|
|
|
|
* should be serialized by pcpu_alloc_mutex.
|
2010-04-09 09:57:01 +00:00
|
|
|
*
|
|
|
|
* RETURNS:
|
2014-09-02 18:46:01 +00:00
|
|
|
* Pointer to temp pages array on success.
|
2010-04-09 09:57:01 +00:00
|
|
|
*/
|
2017-02-25 20:59:26 +00:00
|
|
|
static struct page **pcpu_get_pages(void)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
|
|
|
static struct page **pages;
|
|
|
|
size_t pages_size = pcpu_nr_units * pcpu_unit_pages * sizeof(pages[0]);
|
|
|
|
|
2014-09-02 18:46:01 +00:00
|
|
|
lockdep_assert_held(&pcpu_alloc_mutex);
|
|
|
|
|
|
|
|
if (!pages)
|
2018-02-16 18:09:58 +00:00
|
|
|
pages = pcpu_mem_zalloc(pages_size, GFP_KERNEL);
|
2010-04-09 09:57:01 +00:00
|
|
|
return pages;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_free_pages - free pages which were allocated for @chunk
|
|
|
|
* @chunk: chunk pages were allocated for
|
|
|
|
* @pages: array of pages to be freed, indexed by pcpu_page_idx()
|
|
|
|
* @page_start: page index of the first page to be freed
|
|
|
|
* @page_end: page index of the last page to be freed + 1
|
|
|
|
*
|
|
|
|
* Free pages [@page_start and @page_end) in @pages for all units.
|
|
|
|
* The pages were allocated for @chunk.
|
|
|
|
*/
|
|
|
|
static void pcpu_free_pages(struct pcpu_chunk *chunk,
|
2014-09-02 18:46:01 +00:00
|
|
|
struct page **pages, int page_start, int page_end)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
|
|
|
unsigned int cpu;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
|
|
for (i = page_start; i < page_end; i++) {
|
|
|
|
struct page *page = pages[pcpu_page_idx(cpu, i)];
|
|
|
|
|
|
|
|
if (page)
|
|
|
|
__free_page(page);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_alloc_pages - allocates pages for @chunk
|
|
|
|
* @chunk: target chunk
|
|
|
|
* @pages: array to put the allocated pages into, indexed by pcpu_page_idx()
|
|
|
|
* @page_start: page index of the first page to be allocated
|
|
|
|
* @page_end: page index of the last page to be allocated + 1
|
2018-02-16 18:07:19 +00:00
|
|
|
* @gfp: allocation flags passed to the underlying allocator
|
2010-04-09 09:57:01 +00:00
|
|
|
*
|
|
|
|
* Allocate pages [@page_start,@page_end) into @pages for all units.
|
|
|
|
* The allocation is for @chunk. Percpu core doesn't care about the
|
|
|
|
* content of @pages and will pass it verbatim to pcpu_map_pages().
|
|
|
|
*/
|
|
|
|
static int pcpu_alloc_pages(struct pcpu_chunk *chunk,
|
2018-02-16 18:07:19 +00:00
|
|
|
struct page **pages, int page_start, int page_end,
|
|
|
|
gfp_t gfp)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
2014-08-15 20:06:06 +00:00
|
|
|
unsigned int cpu, tcpu;
|
2010-04-09 09:57:01 +00:00
|
|
|
int i;
|
|
|
|
|
2018-02-16 18:09:58 +00:00
|
|
|
gfp |= __GFP_HIGHMEM;
|
2018-02-16 18:07:19 +00:00
|
|
|
|
2010-04-09 09:57:01 +00:00
|
|
|
for_each_possible_cpu(cpu) {
|
|
|
|
for (i = page_start; i < page_end; i++) {
|
|
|
|
struct page **pagep = &pages[pcpu_page_idx(cpu, i)];
|
|
|
|
|
|
|
|
*pagep = alloc_pages_node(cpu_to_node(cpu), gfp, 0);
|
2014-08-15 20:06:06 +00:00
|
|
|
if (!*pagep)
|
|
|
|
goto err;
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2014-08-15 20:06:06 +00:00
|
|
|
|
|
|
|
err:
|
|
|
|
while (--i >= page_start)
|
|
|
|
__free_page(pages[pcpu_page_idx(cpu, i)]);
|
|
|
|
|
|
|
|
for_each_possible_cpu(tcpu) {
|
|
|
|
if (tcpu == cpu)
|
|
|
|
break;
|
|
|
|
for (i = page_start; i < page_end; i++)
|
|
|
|
__free_page(pages[pcpu_page_idx(tcpu, i)]);
|
|
|
|
}
|
|
|
|
return -ENOMEM;
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_pre_unmap_flush - flush cache prior to unmapping
|
|
|
|
* @chunk: chunk the regions to be flushed belongs to
|
|
|
|
* @page_start: page index of the first page to be flushed
|
|
|
|
* @page_end: page index of the last page to be flushed + 1
|
|
|
|
*
|
|
|
|
* Pages in [@page_start,@page_end) of @chunk are about to be
|
|
|
|
* unmapped. Flush cache. As each flushing trial can be very
|
|
|
|
* expensive, issue flush on the whole region at once rather than
|
|
|
|
* doing it for each cpu. This could be an overkill but is more
|
|
|
|
* scalable.
|
|
|
|
*/
|
|
|
|
static void pcpu_pre_unmap_flush(struct pcpu_chunk *chunk,
|
|
|
|
int page_start, int page_end)
|
|
|
|
{
|
|
|
|
flush_cache_vunmap(
|
2011-11-18 18:55:35 +00:00
|
|
|
pcpu_chunk_addr(chunk, pcpu_low_unit_cpu, page_start),
|
|
|
|
pcpu_chunk_addr(chunk, pcpu_high_unit_cpu, page_end));
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __pcpu_unmap_pages(unsigned long addr, int nr_pages)
|
|
|
|
{
|
2021-04-30 05:59:01 +00:00
|
|
|
vunmap_range_noflush(addr, addr + (nr_pages << PAGE_SHIFT));
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_unmap_pages - unmap pages out of a pcpu_chunk
|
|
|
|
* @chunk: chunk of interest
|
|
|
|
* @pages: pages array which can be used to pass information to free
|
|
|
|
* @page_start: page index of the first page to unmap
|
|
|
|
* @page_end: page index of the last page to unmap + 1
|
|
|
|
*
|
|
|
|
* For each cpu, unmap pages [@page_start,@page_end) out of @chunk.
|
|
|
|
* Corresponding elements in @pages were cleared by the caller and can
|
|
|
|
* be used to carry information to pcpu_free_pages() which will be
|
|
|
|
* called after all unmaps are finished. The caller should call
|
|
|
|
* proper pre/post flush functions.
|
|
|
|
*/
|
|
|
|
static void pcpu_unmap_pages(struct pcpu_chunk *chunk,
|
2014-09-02 18:46:01 +00:00
|
|
|
struct page **pages, int page_start, int page_end)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
|
|
|
unsigned int cpu;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
|
|
for (i = page_start; i < page_end; i++) {
|
|
|
|
struct page *page;
|
|
|
|
|
|
|
|
page = pcpu_chunk_page(chunk, cpu, i);
|
|
|
|
WARN_ON(!page);
|
|
|
|
pages[pcpu_page_idx(cpu, i)] = page;
|
|
|
|
}
|
|
|
|
__pcpu_unmap_pages(pcpu_chunk_addr(chunk, cpu, page_start),
|
|
|
|
page_end - page_start);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_post_unmap_tlb_flush - flush TLB after unmapping
|
|
|
|
* @chunk: pcpu_chunk the regions to be flushed belong to
|
|
|
|
* @page_start: page index of the first page to be flushed
|
|
|
|
* @page_end: page index of the last page to be flushed + 1
|
|
|
|
*
|
|
|
|
* Pages [@page_start,@page_end) of @chunk have been unmapped. Flush
|
|
|
|
* TLB for the regions. This can be skipped if the area is to be
|
|
|
|
* returned to vmalloc as vmalloc will handle TLB flushing lazily.
|
|
|
|
*
|
|
|
|
* As with pcpu_pre_unmap_flush(), TLB flushing also is done at once
|
|
|
|
* for the whole region.
|
|
|
|
*/
|
|
|
|
static void pcpu_post_unmap_tlb_flush(struct pcpu_chunk *chunk,
|
|
|
|
int page_start, int page_end)
|
|
|
|
{
|
|
|
|
flush_tlb_kernel_range(
|
2011-11-18 18:55:35 +00:00
|
|
|
pcpu_chunk_addr(chunk, pcpu_low_unit_cpu, page_start),
|
|
|
|
pcpu_chunk_addr(chunk, pcpu_high_unit_cpu, page_end));
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __pcpu_map_pages(unsigned long addr, struct page **pages,
|
|
|
|
int nr_pages)
|
|
|
|
{
|
2021-04-30 05:58:53 +00:00
|
|
|
return vmap_pages_range_noflush(addr, addr + (nr_pages << PAGE_SHIFT),
|
|
|
|
PAGE_KERNEL, pages, PAGE_SHIFT);
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_map_pages - map pages into a pcpu_chunk
|
|
|
|
* @chunk: chunk of interest
|
|
|
|
* @pages: pages array containing pages to be mapped
|
|
|
|
* @page_start: page index of the first page to map
|
|
|
|
* @page_end: page index of the last page to map + 1
|
|
|
|
*
|
|
|
|
* For each cpu, map pages [@page_start,@page_end) into @chunk. The
|
|
|
|
* caller is responsible for calling pcpu_post_map_flush() after all
|
|
|
|
* mappings are complete.
|
|
|
|
*
|
2014-09-02 18:46:01 +00:00
|
|
|
* This function is responsible for setting up whatever is necessary for
|
|
|
|
* reverse lookup (addr -> chunk).
|
2010-04-09 09:57:01 +00:00
|
|
|
*/
|
|
|
|
static int pcpu_map_pages(struct pcpu_chunk *chunk,
|
2014-09-02 18:46:01 +00:00
|
|
|
struct page **pages, int page_start, int page_end)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
|
|
|
unsigned int cpu, tcpu;
|
|
|
|
int i, err;
|
|
|
|
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
|
|
err = __pcpu_map_pages(pcpu_chunk_addr(chunk, cpu, page_start),
|
|
|
|
&pages[pcpu_page_idx(cpu, page_start)],
|
|
|
|
page_end - page_start);
|
|
|
|
if (err < 0)
|
|
|
|
goto err;
|
|
|
|
|
2014-09-02 18:46:01 +00:00
|
|
|
for (i = page_start; i < page_end; i++)
|
2010-04-09 09:57:01 +00:00
|
|
|
pcpu_set_page_chunk(pages[pcpu_page_idx(cpu, i)],
|
|
|
|
chunk);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
for_each_possible_cpu(tcpu) {
|
|
|
|
if (tcpu == cpu)
|
|
|
|
break;
|
|
|
|
__pcpu_unmap_pages(pcpu_chunk_addr(chunk, tcpu, page_start),
|
|
|
|
page_end - page_start);
|
|
|
|
}
|
2014-08-15 20:06:10 +00:00
|
|
|
pcpu_post_unmap_tlb_flush(chunk, page_start, page_end);
|
2010-04-09 09:57:01 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_post_map_flush - flush cache after mapping
|
|
|
|
* @chunk: pcpu_chunk the regions to be flushed belong to
|
|
|
|
* @page_start: page index of the first page to be flushed
|
|
|
|
* @page_end: page index of the last page to be flushed + 1
|
|
|
|
*
|
|
|
|
* Pages [@page_start,@page_end) of @chunk have been mapped. Flush
|
|
|
|
* cache.
|
|
|
|
*
|
|
|
|
* As with pcpu_pre_unmap_flush(), TLB flushing also is done at once
|
|
|
|
* for the whole region.
|
|
|
|
*/
|
|
|
|
static void pcpu_post_map_flush(struct pcpu_chunk *chunk,
|
|
|
|
int page_start, int page_end)
|
|
|
|
{
|
|
|
|
flush_cache_vmap(
|
2011-11-18 18:55:35 +00:00
|
|
|
pcpu_chunk_addr(chunk, pcpu_low_unit_cpu, page_start),
|
|
|
|
pcpu_chunk_addr(chunk, pcpu_high_unit_cpu, page_end));
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_populate_chunk - populate and map an area of a pcpu_chunk
|
|
|
|
* @chunk: chunk of interest
|
2014-09-02 18:46:02 +00:00
|
|
|
* @page_start: the start page
|
|
|
|
* @page_end: the end page
|
2018-02-16 18:07:19 +00:00
|
|
|
* @gfp: allocation flags passed to the underlying memory allocator
|
2010-04-09 09:57:01 +00:00
|
|
|
*
|
|
|
|
* For each cpu, populate and map pages [@page_start,@page_end) into
|
2014-09-02 18:46:01 +00:00
|
|
|
* @chunk.
|
2010-04-09 09:57:01 +00:00
|
|
|
*
|
|
|
|
* CONTEXT:
|
|
|
|
* pcpu_alloc_mutex, does GFP_KERNEL allocation.
|
|
|
|
*/
|
2014-09-02 18:46:02 +00:00
|
|
|
static int pcpu_populate_chunk(struct pcpu_chunk *chunk,
|
2018-02-16 18:07:19 +00:00
|
|
|
int page_start, int page_end, gfp_t gfp)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
|
|
|
struct page **pages;
|
|
|
|
|
2017-02-25 20:59:26 +00:00
|
|
|
pages = pcpu_get_pages();
|
2010-04-09 09:57:01 +00:00
|
|
|
if (!pages)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-02-16 18:07:19 +00:00
|
|
|
if (pcpu_alloc_pages(chunk, pages, page_start, page_end, gfp))
|
2014-09-02 18:46:02 +00:00
|
|
|
return -ENOMEM;
|
2010-04-09 09:57:01 +00:00
|
|
|
|
2014-09-02 18:46:02 +00:00
|
|
|
if (pcpu_map_pages(chunk, pages, page_start, page_end)) {
|
|
|
|
pcpu_free_pages(chunk, pages, page_start, page_end);
|
|
|
|
return -ENOMEM;
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
pcpu_post_map_flush(chunk, page_start, page_end);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_depopulate_chunk - depopulate and unmap an area of a pcpu_chunk
|
|
|
|
* @chunk: chunk to depopulate
|
2014-09-02 18:46:02 +00:00
|
|
|
* @page_start: the start page
|
|
|
|
* @page_end: the end page
|
2010-04-09 09:57:01 +00:00
|
|
|
*
|
|
|
|
* For each cpu, depopulate and unmap pages [@page_start,@page_end)
|
2014-09-02 18:46:02 +00:00
|
|
|
* from @chunk.
|
2010-04-09 09:57:01 +00:00
|
|
|
*
|
2021-07-03 03:49:57 +00:00
|
|
|
* Caller is required to call pcpu_post_unmap_tlb_flush() if not returning the
|
|
|
|
* region back to vmalloc() which will lazily flush the tlb.
|
|
|
|
*
|
2010-04-09 09:57:01 +00:00
|
|
|
* CONTEXT:
|
|
|
|
* pcpu_alloc_mutex.
|
|
|
|
*/
|
2014-09-02 18:46:02 +00:00
|
|
|
static void pcpu_depopulate_chunk(struct pcpu_chunk *chunk,
|
|
|
|
int page_start, int page_end)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
|
|
|
struct page **pages;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If control reaches here, there must have been at least one
|
|
|
|
* successful population attempt so the temp pages array must
|
|
|
|
* be available now.
|
|
|
|
*/
|
2017-02-25 20:59:26 +00:00
|
|
|
pages = pcpu_get_pages();
|
2010-04-09 09:57:01 +00:00
|
|
|
BUG_ON(!pages);
|
|
|
|
|
|
|
|
/* unmap and free */
|
|
|
|
pcpu_pre_unmap_flush(chunk, page_start, page_end);
|
|
|
|
|
2014-09-02 18:46:02 +00:00
|
|
|
pcpu_unmap_pages(chunk, pages, page_start, page_end);
|
2010-04-09 09:57:01 +00:00
|
|
|
|
2014-09-02 18:46:02 +00:00
|
|
|
pcpu_free_pages(chunk, pages, page_start, page_end);
|
2010-04-09 09:57:01 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 01:09:31 +00:00
|
|
|
static struct pcpu_chunk *pcpu_create_chunk(gfp_t gfp)
|
2010-04-09 09:57:01 +00:00
|
|
|
{
|
|
|
|
struct pcpu_chunk *chunk;
|
|
|
|
struct vm_struct **vms;
|
|
|
|
|
2021-06-03 01:09:31 +00:00
|
|
|
chunk = pcpu_alloc_chunk(gfp);
|
2010-04-09 09:57:01 +00:00
|
|
|
if (!chunk)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
vms = pcpu_get_vm_areas(pcpu_group_offsets, pcpu_group_sizes,
|
2011-01-13 23:46:01 +00:00
|
|
|
pcpu_nr_groups, pcpu_atom_size);
|
2010-04-09 09:57:01 +00:00
|
|
|
if (!vms) {
|
|
|
|
pcpu_free_chunk(chunk);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
chunk->data = vms;
|
|
|
|
chunk->base_addr = vms[0]->addr - pcpu_group_offsets[0];
|
2017-06-19 23:28:31 +00:00
|
|
|
|
|
|
|
pcpu_stats_chunk_alloc();
|
2017-06-19 23:28:32 +00:00
|
|
|
trace_percpu_create_chunk(chunk->base_addr);
|
2017-06-19 23:28:31 +00:00
|
|
|
|
2010-04-09 09:57:01 +00:00
|
|
|
return chunk;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pcpu_destroy_chunk(struct pcpu_chunk *chunk)
|
|
|
|
{
|
2017-06-29 14:56:26 +00:00
|
|
|
if (!chunk)
|
|
|
|
return;
|
|
|
|
|
2017-06-19 23:28:31 +00:00
|
|
|
pcpu_stats_chunk_dealloc();
|
2017-06-19 23:28:32 +00:00
|
|
|
trace_percpu_destroy_chunk(chunk->base_addr);
|
2017-06-19 23:28:31 +00:00
|
|
|
|
2017-06-29 14:56:26 +00:00
|
|
|
if (chunk->data)
|
2010-04-09 09:57:01 +00:00
|
|
|
pcpu_free_vm_areas(chunk->data, pcpu_nr_groups);
|
|
|
|
pcpu_free_chunk(chunk);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct page *pcpu_addr_to_page(void *addr)
|
|
|
|
{
|
|
|
|
return vmalloc_to_page(addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init pcpu_verify_alloc_info(const struct pcpu_alloc_info *ai)
|
|
|
|
{
|
|
|
|
/* no extra restriction */
|
|
|
|
return 0;
|
|
|
|
}
|
percpu: implement partial chunk depopulation
From Roman ("percpu: partial chunk depopulation"):
In our [Facebook] production experience the percpu memory allocator is
sometimes struggling with returning the memory to the system. A typical
example is a creation of several thousands memory cgroups (each has
several chunks of the percpu data used for vmstats, vmevents,
ref counters etc). Deletion and complete releasing of these cgroups
doesn't always lead to a shrinkage of the percpu memory, so that
sometimes there are several GB's of memory wasted.
The underlying problem is the fragmentation: to release an underlying
chunk all percpu allocations should be released first. The percpu
allocator tends to top up chunks to improve the utilization. It means
new small-ish allocations (e.g. percpu ref counters) are placed onto
almost filled old-ish chunks, effectively pinning them in memory.
This patchset solves this problem by implementing a partial depopulation
of percpu chunks: chunks with many empty pages are being asynchronously
depopulated and the pages are returned to the system.
To illustrate the problem the following script can be used:
--
cd /sys/fs/cgroup
mkdir percpu_test
echo "+memory" > percpu_test/cgroup.subtree_control
cat /proc/meminfo | grep Percpu
for i in `seq 1 1000`; do
mkdir percpu_test/cg_"${i}"
for j in `seq 1 10`; do
mkdir percpu_test/cg_"${i}"_"${j}"
done
done
cat /proc/meminfo | grep Percpu
for i in `seq 1 1000`; do
for j in `seq 1 10`; do
rmdir percpu_test/cg_"${i}"_"${j}"
done
done
sleep 10
cat /proc/meminfo | grep Percpu
for i in `seq 1 1000`; do
rmdir percpu_test/cg_"${i}"
done
rmdir percpu_test
--
It creates 11000 memory cgroups and removes every 10 out of 11.
It prints the initial size of the percpu memory, the size after
creating all cgroups and the size after deleting most of them.
Results:
vanilla:
./percpu_test.sh
Percpu: 7488 kB
Percpu: 481152 kB
Percpu: 481152 kB
with this patchset applied:
./percpu_test.sh
Percpu: 7488 kB
Percpu: 481408 kB
Percpu: 135552 kB
The total size of the percpu memory was reduced by more than 3.5 times.
This patch:
This patch implements partial depopulation of percpu chunks.
As of now, a chunk can be depopulated only as a part of the final
destruction, if there are no more outstanding allocations. However
to minimize a memory waste it might be useful to depopulate a
partially filed chunk, if a small number of outstanding allocations
prevents the chunk from being fully reclaimed.
This patch implements the following depopulation process: it scans
over the chunk pages, looks for a range of empty and populated pages
and performs the depopulation. To avoid races with new allocations,
the chunk is previously isolated. After the depopulation the chunk is
sidelined to a special list or freed. New allocations prefer using
active chunks to sidelined chunks. If a sidelined chunk is used, it is
reintegrated to the active lists.
The depopulation is scheduled on the free path if the chunk is all of
the following:
1) has more than 1/4 of total pages free and populated
2) the system has enough free percpu pages aside of this chunk
3) isn't the reserved chunk
4) isn't the first chunk
If it's already depopulated but got free populated pages, it's a good
target too. The chunk is moved to a special slot,
pcpu_to_depopulate_slot, chunk->isolated is set, and the balance work
item is scheduled. On isolation, these pages are removed from the
pcpu_nr_empty_pop_pages. It is constantly replaced to the
to_depopulate_slot when it meets these qualifications.
pcpu_reclaim_populated() iterates over the to_depopulate_slot until it
becomes empty. The depopulation is performed in the reverse direction to
keep populated pages close to the beginning. Depopulated chunks are
sidelined to preferentially avoid them for new allocations. When no
active chunk can suffice a new allocation, sidelined chunks are first
checked before creating a new chunk.
Signed-off-by: Roman Gushchin <guro@fb.com>
Co-developed-by: Dennis Zhou <dennis@kernel.org>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Tested-by: Pratik Sampat <psampat@linux.ibm.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
2021-04-08 03:57:36 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* pcpu_should_reclaim_chunk - determine if a chunk should go into reclaim
|
|
|
|
* @chunk: chunk of interest
|
|
|
|
*
|
|
|
|
* This is the entry point for percpu reclaim. If a chunk qualifies, it is then
|
|
|
|
* isolated and managed in separate lists at the back of pcpu_slot: sidelined
|
|
|
|
* and to_depopulate respectively. The to_depopulate list holds chunks slated
|
|
|
|
* for depopulation. They no longer contribute to pcpu_nr_empty_pop_pages once
|
|
|
|
* they are on this list. Once depopulated, they are moved onto the sidelined
|
|
|
|
* list which enables them to be pulled back in for allocation if no other chunk
|
|
|
|
* can suffice the allocation.
|
|
|
|
*/
|
|
|
|
static bool pcpu_should_reclaim_chunk(struct pcpu_chunk *chunk)
|
|
|
|
{
|
|
|
|
/* do not reclaim either the first chunk or reserved chunk */
|
|
|
|
if (chunk == pcpu_first_chunk || chunk == pcpu_reserved_chunk)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If it is isolated, it may be on the sidelined list so move it back to
|
|
|
|
* the to_depopulate list. If we hit at least 1/4 pages empty pages AND
|
|
|
|
* there is no system-wide shortage of empty pages aside from this
|
|
|
|
* chunk, move it to the to_depopulate list.
|
|
|
|
*/
|
|
|
|
return ((chunk->isolated && chunk->nr_empty_pop_pages) ||
|
2021-06-03 01:09:31 +00:00
|
|
|
(pcpu_nr_empty_pop_pages >
|
|
|
|
(PCPU_EMPTY_POP_PAGES_HIGH + chunk->nr_empty_pop_pages) &&
|
|
|
|
chunk->nr_empty_pop_pages >= chunk->nr_pages / 4));
|
percpu: implement partial chunk depopulation
From Roman ("percpu: partial chunk depopulation"):
In our [Facebook] production experience the percpu memory allocator is
sometimes struggling with returning the memory to the system. A typical
example is a creation of several thousands memory cgroups (each has
several chunks of the percpu data used for vmstats, vmevents,
ref counters etc). Deletion and complete releasing of these cgroups
doesn't always lead to a shrinkage of the percpu memory, so that
sometimes there are several GB's of memory wasted.
The underlying problem is the fragmentation: to release an underlying
chunk all percpu allocations should be released first. The percpu
allocator tends to top up chunks to improve the utilization. It means
new small-ish allocations (e.g. percpu ref counters) are placed onto
almost filled old-ish chunks, effectively pinning them in memory.
This patchset solves this problem by implementing a partial depopulation
of percpu chunks: chunks with many empty pages are being asynchronously
depopulated and the pages are returned to the system.
To illustrate the problem the following script can be used:
--
cd /sys/fs/cgroup
mkdir percpu_test
echo "+memory" > percpu_test/cgroup.subtree_control
cat /proc/meminfo | grep Percpu
for i in `seq 1 1000`; do
mkdir percpu_test/cg_"${i}"
for j in `seq 1 10`; do
mkdir percpu_test/cg_"${i}"_"${j}"
done
done
cat /proc/meminfo | grep Percpu
for i in `seq 1 1000`; do
for j in `seq 1 10`; do
rmdir percpu_test/cg_"${i}"_"${j}"
done
done
sleep 10
cat /proc/meminfo | grep Percpu
for i in `seq 1 1000`; do
rmdir percpu_test/cg_"${i}"
done
rmdir percpu_test
--
It creates 11000 memory cgroups and removes every 10 out of 11.
It prints the initial size of the percpu memory, the size after
creating all cgroups and the size after deleting most of them.
Results:
vanilla:
./percpu_test.sh
Percpu: 7488 kB
Percpu: 481152 kB
Percpu: 481152 kB
with this patchset applied:
./percpu_test.sh
Percpu: 7488 kB
Percpu: 481408 kB
Percpu: 135552 kB
The total size of the percpu memory was reduced by more than 3.5 times.
This patch:
This patch implements partial depopulation of percpu chunks.
As of now, a chunk can be depopulated only as a part of the final
destruction, if there are no more outstanding allocations. However
to minimize a memory waste it might be useful to depopulate a
partially filed chunk, if a small number of outstanding allocations
prevents the chunk from being fully reclaimed.
This patch implements the following depopulation process: it scans
over the chunk pages, looks for a range of empty and populated pages
and performs the depopulation. To avoid races with new allocations,
the chunk is previously isolated. After the depopulation the chunk is
sidelined to a special list or freed. New allocations prefer using
active chunks to sidelined chunks. If a sidelined chunk is used, it is
reintegrated to the active lists.
The depopulation is scheduled on the free path if the chunk is all of
the following:
1) has more than 1/4 of total pages free and populated
2) the system has enough free percpu pages aside of this chunk
3) isn't the reserved chunk
4) isn't the first chunk
If it's already depopulated but got free populated pages, it's a good
target too. The chunk is moved to a special slot,
pcpu_to_depopulate_slot, chunk->isolated is set, and the balance work
item is scheduled. On isolation, these pages are removed from the
pcpu_nr_empty_pop_pages. It is constantly replaced to the
to_depopulate_slot when it meets these qualifications.
pcpu_reclaim_populated() iterates over the to_depopulate_slot until it
becomes empty. The depopulation is performed in the reverse direction to
keep populated pages close to the beginning. Depopulated chunks are
sidelined to preferentially avoid them for new allocations. When no
active chunk can suffice a new allocation, sidelined chunks are first
checked before creating a new chunk.
Signed-off-by: Roman Gushchin <guro@fb.com>
Co-developed-by: Dennis Zhou <dennis@kernel.org>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Tested-by: Pratik Sampat <psampat@linux.ibm.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
2021-04-08 03:57:36 +00:00
|
|
|
}
|