Skip to content

Commit

Permalink
alloc_tag: support for page allocation tag compression
Browse files Browse the repository at this point in the history
Implement support for storing page allocation tag references directly in
the page flags instead of page extensions.  sysctl.vm.mem_profiling boot
parameter it extended to provide a way for a user to request this mode. 
Enabling compression eliminates memory overhead caused by page_ext and
results in better performance for page allocations.  However this mode
will not work if the number of available page flag bits is insufficient to
address all kernel allocations.  Such condition can happen during boot or
when loading a module.  If this condition is detected, memory allocation
profiling gets disabled with an appropriate warning.  By default
compression mode is disabled.

Link: https://lkml.kernel.org/r/[email protected]
Signed-off-by: Suren Baghdasaryan <[email protected]>
Reviewed-by: Pasha Tatashin <[email protected]>
Cc: Ard Biesheuvel <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Borislav Petkov (AMD) <[email protected]>
Cc: Christoph Hellwig <[email protected]>
Cc: Daniel Gomez <[email protected]>
Cc: David Hildenbrand <[email protected]>
Cc: Davidlohr Bueso <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Dennis Zhou <[email protected]>
Cc: Johannes Weiner <[email protected]>
Cc: John Hubbard <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: Joonsoo Kim <[email protected]>
Cc: Kalesh Singh <[email protected]>
Cc: Kees Cook <[email protected]>
Cc: Kent Overstreet <[email protected]>
Cc: Liam R. Howlett <[email protected]>
Cc: Luis Chamberlain <[email protected]>
Cc: Matthew Wilcox <[email protected]>
Cc: Michal Hocko <[email protected]>
Cc: Mike Rapoport (Microsoft) <[email protected]>
Cc: Minchan Kim <[email protected]>
Cc: Paul E. McKenney <[email protected]>
Cc: Petr Pavlu <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: Sami Tolvanen <[email protected]>
Cc: Sourav Panda <[email protected]>
Cc: Steven Rostedt (Google) <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: Thomas Huth <[email protected]>
Cc: Uladzislau Rezki (Sony) <[email protected]>
Cc: Vlastimil Babka <[email protected]>
Cc: Xiongwei Song <[email protected]>
Cc: Yu Zhao <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
  • Loading branch information
surenbaghdasaryan authored and akpm00 committed Nov 7, 2024
1 parent 42895a8 commit 4835f74
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 33 deletions.
7 changes: 6 additions & 1 deletion Documentation/mm/allocation-profiling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ kconfig options:
missing annotation

Boot parameter:
sysctl.vm.mem_profiling=0|1|never
sysctl.vm.mem_profiling={0|1|never}[,compressed]

When set to "never", memory allocation profiling overhead is minimized and it
cannot be enabled at runtime (sysctl becomes read-only).
When CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT=y, default value is "1".
When CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT=n, default value is "never".
"compressed" optional parameter will try to store page tag references in a
compact format, avoiding page extensions. This results in improved performance
and memory consumption, however it might fail depending on system configuration.
If compression fails, a warning is issued and memory allocation profiling gets
disabled.

sysctl:
/proc/sys/vm/mem_profiling
Expand Down
10 changes: 9 additions & 1 deletion include/linux/alloc_tag.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,16 @@ struct alloc_tag {
struct alloc_tag_counters __percpu *counters;
} __aligned(8);

struct alloc_tag_kernel_section {
struct alloc_tag *first_tag;
unsigned long count;
};

struct alloc_tag_module_section {
unsigned long start_addr;
union {
unsigned long start_addr;
struct alloc_tag *first_tag;
};
unsigned long end_addr;
/* used size */
unsigned long size;
Expand Down
3 changes: 3 additions & 0 deletions include/linux/codetag.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ struct codetag_module;
struct seq_buf;
struct module;

#define CODETAG_SECTION_START_PREFIX "__start_"
#define CODETAG_SECTION_STOP_PREFIX "__stop_"

/*
* An instance of this structure is created in a special ELF section at every
* code location being tagged. At runtime, the special section is treated as
Expand Down
7 changes: 7 additions & 0 deletions include/linux/page-flags-layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,12 @@
ZONES_WIDTH - LRU_GEN_WIDTH - SECTIONS_WIDTH - \
NODES_WIDTH - KASAN_TAG_WIDTH - LAST_CPUPID_WIDTH)

#define NR_NON_PAGEFLAG_BITS (SECTIONS_WIDTH + NODES_WIDTH + ZONES_WIDTH + \
LAST_CPUPID_SHIFT + KASAN_TAG_WIDTH + \
LRU_GEN_WIDTH + LRU_REFS_WIDTH)

#define NR_UNUSED_PAGEFLAG_BITS (BITS_PER_LONG - \
(NR_NON_PAGEFLAG_BITS + NR_PAGEFLAGS))

#endif
#endif /* _LINUX_PAGE_FLAGS_LAYOUT */
145 changes: 128 additions & 17 deletions include/linux/pgalloc_tag.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,118 @@

#include <linux/page_ext.h>

extern struct page_ext_operations page_alloc_tagging_ops;
extern unsigned long alloc_tag_ref_mask;
extern int alloc_tag_ref_offs;
extern struct alloc_tag_kernel_section kernel_tags;

DECLARE_STATIC_KEY_FALSE(mem_profiling_compressed);

typedef u16 pgalloc_tag_idx;

union pgtag_ref_handle {
union codetag_ref *ref; /* reference in page extension */
struct page *page; /* reference in page flags */
};

extern struct page_ext_operations page_alloc_tagging_ops;
/* Reserved indexes */
#define CODETAG_ID_NULL 0
#define CODETAG_ID_EMPTY 1
#define CODETAG_ID_FIRST 2

#ifdef CONFIG_MODULES

extern struct alloc_tag_module_section module_tags;

static inline struct alloc_tag *module_idx_to_tag(pgalloc_tag_idx idx)
{
return &module_tags.first_tag[idx - kernel_tags.count];
}

static inline pgalloc_tag_idx module_tag_to_idx(struct alloc_tag *tag)
{
return CODETAG_ID_FIRST + kernel_tags.count + (tag - module_tags.first_tag);
}

#else /* CONFIG_MODULES */

static inline struct alloc_tag *module_idx_to_tag(pgalloc_tag_idx idx)
{
pr_warn("invalid page tag reference %lu\n", (unsigned long)idx);
return NULL;
}

static inline pgalloc_tag_idx module_tag_to_idx(struct alloc_tag *tag)
{
pr_warn("invalid page tag 0x%lx\n", (unsigned long)tag);
return CODETAG_ID_NULL;
}

#endif /* CONFIG_MODULES */

static inline void idx_to_ref(pgalloc_tag_idx idx, union codetag_ref *ref)
{
switch (idx) {
case (CODETAG_ID_NULL):
ref->ct = NULL;
break;
case (CODETAG_ID_EMPTY):
set_codetag_empty(ref);
break;
default:
idx -= CODETAG_ID_FIRST;
ref->ct = idx < kernel_tags.count ?
&kernel_tags.first_tag[idx].ct :
&module_idx_to_tag(idx)->ct;
break;
}
}

static inline pgalloc_tag_idx ref_to_idx(union codetag_ref *ref)
{
struct alloc_tag *tag;

if (!ref->ct)
return CODETAG_ID_NULL;

if (is_codetag_empty(ref))
return CODETAG_ID_EMPTY;

tag = ct_to_alloc_tag(ref->ct);
if (tag >= kernel_tags.first_tag && tag < kernel_tags.first_tag + kernel_tags.count)
return CODETAG_ID_FIRST + (tag - kernel_tags.first_tag);

return module_tag_to_idx(tag);
}



/* Should be called only if mem_alloc_profiling_enabled() */
static inline bool get_page_tag_ref(struct page *page, union codetag_ref *ref,
union pgtag_ref_handle *handle)
{
struct page_ext *page_ext;
union codetag_ref *tmp;

if (!page)
return false;

page_ext = page_ext_get(page);
if (!page_ext)
return false;
if (static_key_enabled(&mem_profiling_compressed)) {
pgalloc_tag_idx idx;

idx = (page->flags >> alloc_tag_ref_offs) & alloc_tag_ref_mask;
idx_to_ref(idx, ref);
handle->page = page;
} else {
struct page_ext *page_ext;
union codetag_ref *tmp;

page_ext = page_ext_get(page);
if (!page_ext)
return false;

tmp = (union codetag_ref *)page_ext_data(page_ext, &page_alloc_tagging_ops);
ref->ct = tmp->ct;
handle->ref = tmp;
}

tmp = (union codetag_ref *)page_ext_data(page_ext, &page_alloc_tagging_ops);
ref->ct = tmp->ct;
handle->ref = tmp;
return true;
}

Expand All @@ -42,16 +131,35 @@ static inline void put_page_tag_ref(union pgtag_ref_handle handle)
if (WARN_ON(!handle.ref))
return;

page_ext_put((void *)handle.ref - page_alloc_tagging_ops.offset);
if (!static_key_enabled(&mem_profiling_compressed))
page_ext_put((void *)handle.ref - page_alloc_tagging_ops.offset);
}

static inline void update_page_tag_ref(union pgtag_ref_handle handle,
union codetag_ref *ref)
static inline void update_page_tag_ref(union pgtag_ref_handle handle, union codetag_ref *ref)
{
if (WARN_ON(!handle.ref || !ref))
return;

handle.ref->ct = ref->ct;
if (static_key_enabled(&mem_profiling_compressed)) {
struct page *page = handle.page;
unsigned long old_flags;
unsigned long flags;
unsigned long idx;

if (WARN_ON(!page || !ref))
return;

idx = (unsigned long)ref_to_idx(ref);
idx = (idx & alloc_tag_ref_mask) << alloc_tag_ref_offs;
do {
old_flags = READ_ONCE(page->flags);
flags = old_flags;
flags &= ~(alloc_tag_ref_mask << alloc_tag_ref_offs);
flags |= idx;
} while (unlikely(!try_cmpxchg(&page->flags, &old_flags, flags)));
} else {
if (WARN_ON(!handle.ref || !ref))
return;

handle.ref->ct = ref->ct;
}
}

static inline void clear_page_tag_ref(struct page *page)
Expand Down Expand Up @@ -122,6 +230,8 @@ static inline void pgalloc_tag_sub_pages(struct alloc_tag *tag, unsigned int nr)
this_cpu_sub(tag->counters->bytes, PAGE_SIZE * nr);
}

void __init alloc_tag_sec_init(void);

#else /* CONFIG_MEM_ALLOC_PROFILING */

static inline void clear_page_tag_ref(struct page *page) {}
Expand All @@ -130,6 +240,7 @@ static inline void pgalloc_tag_add(struct page *page, struct task_struct *task,
static inline void pgalloc_tag_sub(struct page *page, unsigned int nr) {}
static inline struct alloc_tag *pgalloc_tag_get(struct page *page) { return NULL; }
static inline void pgalloc_tag_sub_pages(struct alloc_tag *tag, unsigned int nr) {}
static inline void alloc_tag_sec_init(void) {}

#endif /* CONFIG_MEM_ALLOC_PROFILING */

Expand Down
Loading

0 comments on commit 4835f74

Please sign in to comment.