mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 11:03:14 -04:00
Patch series "Keep tail page private zero at free and folio split", v3.
This patchset makes sure tail_page->private is zero before compound or
high-order pages are returned to the allocator. It also checks tail pages
that become new folio heads during large folio split, before their private
fields are used by new folios.
Note on ZONE_DEVICE and DAX page/folio
===
ZONE_DEVICE and DAX use prep_compound_tail() to reinitialize folios, so
tail_page->private was reset before this patchset. There was a concern
that after this patchset stale ->private can appear after ZONE_DEVICE/DAX
folio initialization. My reasoning is that no code sets ZONE_DEVICE/DAX
page->private, so their page->private stays zero all the time.
ZONE_DEVICE_PRIVATE page migration only supports anonymous memory without
swapcache, so after the migration ->private remains zero.
But let me know if my reasoning is wrong. It can be fixed by adding
->private zeroing code in ZONE_DEVICE/DAX folio initialization code.
Motivation
===
page->private is zeroed at page free time since commit ac1ea21959
("mm/page_alloc: clear page->private in free_pages_prepare()"), since we
concluded that it might be too much to ask every page user to free a page
with ->private zeroed. The holder of the last page reference might not
know whether ->private needs to be cleared.
For compound and high-order pages, tail_page->private can also leak to
later users if it is left uncleared. The page allocation path does not
zero every tail_page->private field, so they can be seen by new users and
cause unexpected issues[1].
Check tail_page->private at page free time, and check tail pages that
become new folio heads during large folio split. With those checks in
place, prep_compound_tail() no longer needs to clear tail_page->private
when preparing compound page metadata.
Overview
===
1. Patch 1 clears all pages ->private before percpu-km frees them.
2. Patch 2 removes setting page->private in compaction code when a free
page is taken out of the buddy allocator. cc->freepages is indexed by
page order, so storing the free page order in page->private is
redundant. In alloc_contig_frozen_range_noprof(),
isolate_freepages_range() is used to grab free pages from buddy
allocator and it leaves the aforementioned page->private set until
either split_free_frozen_pages() or prep_new_page() is called. That
stale value without resetting triggers the tail_page->private nonzero
check once set_page_private(0) is removed from prep_compound_tail().
3. Patch 3 adds back the page->private check for tail pages promoted to new
folio heads in __split_folio_to_order().
4. Patch 4 adds a tail_page->private check in the page free path.
5. Patch 5 removes tail_page->private zeroing from prep_compound_tail().
This patch (of 5):
page->private is cleared in free page path. In a subsequent commit,
tail_page->private will be checked and ensured to be zero. Clearing
percpu-km allocated pages' ->private to prevent triggering warnings later,
namely undo what we did in pcpu_create_chunk().
Link: https://lore.kernel.org/20260709-keep-subpage-private-zero-at-free-v3-0-7e4fe155f5b9@nvidia.com
Link: https://lore.kernel.org/20260709-keep-subpage-private-zero-at-free-v3-1-7e4fe155f5b9@nvidia.com
Link: https://lore.kernel.org/all/20260206174017.128673-1-mikhail.v.gavrilov@gmail.com/ [1]
Signed-off-by: Zi Yan <ziy@nvidia.com>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Cc: Alistair Popple <apopple@nvidia.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Brendan Jackman <jackmanb@google.com>
Cc: Brendan Jackman <brendan.jackman@linux.dev>
Cc: Dennis Zhou <dennis@kernel.org>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Liam R. Howlett <liam@infradead.org>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Nico Pache <npache@redhat.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Tejun Heo <tj@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
138 lines
3.4 KiB
C
138 lines
3.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* mm/percpu-km.c - kernel memory based chunk allocation
|
|
*
|
|
* Copyright (C) 2010 SUSE Linux Products GmbH
|
|
* Copyright (C) 2010 Tejun Heo <tj@kernel.org>
|
|
*
|
|
* Chunks are allocated as a contiguous kernel memory using gfp
|
|
* allocation. This is to be used on nommu architectures.
|
|
*
|
|
* To use percpu-km,
|
|
*
|
|
* - define CONFIG_NEED_PER_CPU_KM from the arch Kconfig.
|
|
*
|
|
* - CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK must not be defined. It's
|
|
* not compatible with PER_CPU_KM. EMBED_FIRST_CHUNK should work
|
|
* fine.
|
|
*
|
|
* - NUMA is not supported. When setting up the first chunk,
|
|
* @cpu_distance_fn should be NULL or report all CPUs to be nearer
|
|
* than or at LOCAL_DISTANCE.
|
|
*
|
|
* - It's best if the chunk size is power of two multiple of
|
|
* PAGE_SIZE. Because each chunk is allocated as a contiguous
|
|
* kernel memory block using alloc_pages(), memory will be wasted if
|
|
* chunk size is not aligned. percpu-km code will whine about it.
|
|
*/
|
|
|
|
#if defined(CONFIG_SMP) && defined(CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK)
|
|
#error "contiguous percpu allocation is incompatible with paged first chunk"
|
|
#endif
|
|
|
|
#include <linux/log2.h>
|
|
|
|
static void pcpu_post_unmap_tlb_flush(struct pcpu_chunk *chunk,
|
|
int page_start, int page_end)
|
|
{
|
|
/* nothing */
|
|
}
|
|
|
|
static int pcpu_populate_chunk(struct pcpu_chunk *chunk,
|
|
int page_start, int page_end, gfp_t gfp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void pcpu_depopulate_chunk(struct pcpu_chunk *chunk,
|
|
int page_start, int page_end)
|
|
{
|
|
/* nada */
|
|
}
|
|
|
|
static struct pcpu_chunk *pcpu_create_chunk(gfp_t gfp)
|
|
{
|
|
const int nr_pages = pcpu_group_sizes[0] >> PAGE_SHIFT;
|
|
struct pcpu_chunk *chunk;
|
|
struct page *pages;
|
|
unsigned long flags;
|
|
int i;
|
|
|
|
chunk = pcpu_alloc_chunk(gfp);
|
|
if (!chunk)
|
|
return NULL;
|
|
|
|
pages = alloc_pages(gfp, order_base_2(nr_pages));
|
|
if (!pages) {
|
|
pcpu_free_chunk(chunk);
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < nr_pages; i++)
|
|
pcpu_set_page_chunk(pages + i, chunk);
|
|
|
|
chunk->data = pages;
|
|
chunk->base_addr = page_address(pages);
|
|
|
|
spin_lock_irqsave(&pcpu_lock, flags);
|
|
pcpu_chunk_populated(chunk, 0, chunk->nr_pages);
|
|
spin_unlock_irqrestore(&pcpu_lock, flags);
|
|
|
|
pcpu_stats_chunk_alloc();
|
|
trace_percpu_create_chunk(chunk->base_addr);
|
|
|
|
return chunk;
|
|
}
|
|
|
|
static void pcpu_destroy_chunk(struct pcpu_chunk *chunk)
|
|
{
|
|
const int nr_pages = pcpu_group_sizes[0] >> PAGE_SHIFT;
|
|
|
|
if (!chunk)
|
|
return;
|
|
|
|
pcpu_stats_chunk_dealloc();
|
|
trace_percpu_destroy_chunk(chunk->base_addr);
|
|
|
|
if (chunk->data) {
|
|
struct page *pages = (struct page *)chunk->data;
|
|
int i;
|
|
|
|
/* clear chunk info from each page before free them */
|
|
for (i = 0; i < nr_pages; i++)
|
|
pcpu_set_page_chunk(pages + i, NULL);
|
|
__free_pages(chunk->data, order_base_2(nr_pages));
|
|
}
|
|
pcpu_free_chunk(chunk);
|
|
}
|
|
|
|
static struct page *pcpu_addr_to_page(void *addr)
|
|
{
|
|
return virt_to_page(addr);
|
|
}
|
|
|
|
static int __init pcpu_verify_alloc_info(const struct pcpu_alloc_info *ai)
|
|
{
|
|
size_t nr_pages, alloc_pages;
|
|
|
|
/* all units must be in a single group */
|
|
if (ai->nr_groups != 1) {
|
|
pr_crit("can't handle more than one group\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nr_pages = (ai->groups[0].nr_units * ai->unit_size) >> PAGE_SHIFT;
|
|
alloc_pages = roundup_pow_of_two(nr_pages);
|
|
|
|
if (alloc_pages > nr_pages)
|
|
pr_warn("wasting %zu pages per chunk\n",
|
|
alloc_pages - nr_pages);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool pcpu_should_reclaim_chunk(struct pcpu_chunk *chunk)
|
|
{
|
|
return false;
|
|
}
|