mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages

HugeTLB vmemmap optimization now uses per-zone shared tail vmemmap pages. 
Device DAX has not been switched to that mechanism yet.

Switch device DAX to vmemmap_shared_tail_page() as well.  This aligns DAX
with HugeTLB by using the common per-zone shared tail vmemmap page.

The optimization is enabled only for DEV-DAX through pgmap->vmemmap_shift,
which supplies the compound page order recorded in section metadata before
vmemmap population.  Unlike FS-DAX, DEV-DAX does not modify tail struct
pages, so sharing them is safe.

Since the shared tail page can now back ZONE_DEVICE vmemmap mappings,
initialize its entries with PG_reserved for device zones.  Also skip
poisoning vmemmap-optimizable sections while their struct pages may be
shared.

Each PTE mapping the shared device DAX tail page takes a page reference. 
A sufficiently large range could therefore cycle the reference count back
to zero if population were allowed to continue after it became
non-positive.  Use try_get_page() so further mappings fail at that point. 
The section population error path tears down mappings created for the
failed section, while the warning makes this currently impractical limit
visible.

[muchun.song@linux.dev: set PG_reserved before preparing the compound tail]
  Link: https://lore.kernel.org/20260930150748.1134516-1-songmuchun@bytedance.com
Link: https://lore.kernel.org/20260930140627.57431-8-songmuchun@bytedance.com
Signed-off-by: Muchun Song <muchun.song@linux.dev>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Cc: David Hildenbrand <david@kernel.org>
Cc: Oscar Salvador <osalvador@suse.de>
Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Nicholas Piggin <npiggin@gmail.com>
Cc: Christophe Leroy <chleroy@kernel.org>
Cc: Ritesh Harjani (IBM) <ritesh.list@gmail.com>
Cc: Shrikanth Hegde <sshegde@linux.ibm.com>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Lance Yang <lance.yang@linux.dev>
This commit is contained in:
Muchun Song 2026-09-30 22:06:22 +08:00 committed by Andrew Morton
parent 2ef77f4a7d
commit ba9164f880
3 changed files with 42 additions and 41 deletions

View File

@ -2143,11 +2143,21 @@ static inline int online_device_section(const struct mem_section *section)
return section && ((section->section_mem_map & flags) == flags);
}
static inline struct zone *device_zone(int nid)
{
return &NODE_DATA(nid)->node_zones[ZONE_DEVICE];
}
#else
static inline int online_device_section(const struct mem_section *section)
{
return 0;
}
static inline struct zone *device_zone(int nid)
{
return NULL;
}
#endif
static inline int online_section_nr(unsigned long nr)

View File

@ -43,6 +43,7 @@
#include "mm_init.h"
#include "page_alloc.h"
#include "shuffle.h"
#include "sparse.h"
enum {
MEMMAP_ON_MEMORY_DISABLE = 0,
@ -554,8 +555,9 @@ void remove_pfn_range_from_zone(struct zone *zone,
/* Select all remaining pages up to the next section boundary */
cur_nr_pages =
min(end_pfn - pfn, SECTION_ALIGN_UP(pfn + 1) - pfn);
page_init_poison(pfn_to_page(pfn),
sizeof(struct page) * cur_nr_pages);
if (!section_vmemmap_optimizable(__pfn_to_section(pfn)))
page_init_poison(pfn_to_page(pfn),
sizeof(struct page) * cur_nr_pages);
}
/*

View File

@ -224,6 +224,8 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
atomic_set(&page->_mapcount, -1);
set_page_node(page, zone_to_nid(zone));
set_page_zone(page, zone_idx(zone));
if (zone_is_zone_device(zone))
__SetPageReserved(page);
prep_compound_tail(page, NULL, order);
}
@ -288,14 +290,18 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
/*
* When a PTE/PMD entry is freed from the init_mm
* there's a free_pages() call to this page allocated
* above. Thus this get_page() is paired with the
* above. Thus this try_get_page() is paired with the
* put_page_testzero() on the freeing path.
* This can only called by certain ZONE_DEVICE path,
* and through vmemmap_populate_compound_pages() when
* slab is available.
*
* Use try_get_page() to prevent the shared page refcount
* from overflowing.
*/
if (flags & VMEMMAP_POPULATE_DAX)
get_page(pfn_to_page(ptpfn));
if ((flags & VMEMMAP_POPULATE_DAX) &&
!try_get_page(pfn_to_page(ptpfn)))
return NULL;
}
entry = pfn_pte(ptpfn, PAGE_KERNEL);
set_pte_at(&init_mm, addr, pte, entry);
@ -529,47 +535,27 @@ static bool __meminit reuse_compound_section(unsigned long start_pfn,
return !IS_ALIGNED(offset, nr_pages) && nr_pages > PAGES_PER_SUBSECTION;
}
static pte_t * __meminit compound_section_tail_page(unsigned long addr)
{
pte_t *pte;
addr -= PAGE_SIZE;
/*
* Assuming sections are populated sequentially, the previous section's
* page data can be reused.
*/
pte = pte_offset_kernel(pmd_off_k(addr), addr);
if (!pte)
return NULL;
return pte;
}
static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
struct dev_pagemap *pgmap)
{
const unsigned long flags = VMEMMAP_POPULATE_DAX;
const unsigned int order = pfn_to_section_compound_order(start_pfn);
unsigned long size, addr;
pte_t *pte;
struct page *page;
int rc;
if (reuse_compound_section(start_pfn, pgmap)) {
pte = compound_section_tail_page(start);
if (!pte)
return -ENOMEM;
page = vmemmap_shared_tail_page(order, device_zone(node));
if (!page)
return -ENOMEM;
/*
* Reuse the page that was populated in the prior iteration
* with just tail struct pages.
*/
if (reuse_compound_section(start_pfn, pgmap))
return vmemmap_populate_range(start, end, node, NULL,
pte_pfn(ptep_get(pte)), flags);
}
page_to_pfn(page), flags);
size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
size = min(end - start, (1UL << order) * sizeof(struct page));
for (addr = start; addr < end; addr += size) {
unsigned long next, last = addr + size;
@ -585,12 +571,12 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
return -ENOMEM;
/*
* Reuse the previous page for the rest of tail pages
* Reuse the shared page for the rest of tail pages
* See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
next += PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
pte_pfn(ptep_get(pte)), flags);
page_to_pfn(page), flags);
if (rc)
return -ENOMEM;
}
@ -922,13 +908,16 @@ int __meminit sparse_add_section(int nid, unsigned long start_pfn,
if (IS_ERR(memmap))
return PTR_ERR(memmap);
/*
* Poison uninitialized struct pages in order to catch invalid flags
* combinations.
*/
page_init_poison(memmap, sizeof(struct page) * nr_pages);
ms = __nr_to_section(section_nr);
/*
* Poison uninitialized struct pages to catch invalid flag combinations.
*
* Tail struct pages in a vmemmap-optimized section are initialized and
* shared during vmemmap population, so they must not be overwritten here.
*/
if (!section_vmemmap_optimizable(ms))
page_init_poison(memmap, sizeof(struct page) * nr_pages);
__section_mark_present(ms, section_nr);
/* Align memmap to section boundary in the subsection case */