Merge tag 'mm-core-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mm/core

Pull core memory management updates from David Hildenbrand:
 "This includes updates to:

   - MEMHP (Memory Hotplug)

   - GUP (Get User Pages)

  The main change is:

   - Reduce zone contiguity checking overhead during memory hot(un)plug
     (Yuan Liu)

  Other changes include smaller fixes along with various cleanups"

* tag 'mm-core-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mm/core:
  mm/memory_hotplug: fix missing rollback in __add_pages()
  mm/memory_hotplug: optimize zone contiguous check when changing pfn range
  mm/memory_hotplug: make shrink_zone_span() more robust
  mm/gup: honour FOLL_PIN in NOMMU __get_user_pages_locked()
  mm/gup_test: report actual pinned bytes
  mm/gup_test: safely calculate GUP batch size
  mm: gup: cleanup the gup_fast_*() call chain
  mm: gup: move pmd_protnone() into gup_fast_pmd_leaf()
This commit is contained in:
Linus Torvalds committed 2026-10-23 09:13:11 -10:00
commit 2ee2475e15
8 files changed
+164 -86

No files matched your search

+6
View File
@@ -480,6 +480,12 @@ General
``present_pages`` should use ``get_online_mems()`` to get a stable value. It
is initialized by ``calculate_node_totalpages()``.
``pages_with_online_memmap``
Pages within the zone that have an online memory map: present pages and
memory holes whose memory map has been initialized and
``pfn_to_online_page()`` succeeds. See the comment for
``pages_with_online_memmap`` in ``include/linux/mmzone.h`` for more details.
``present_early_pages``
The present pages existing within the zone located on memory available since
early boot, excluding hotplugged memory. Defined only when
+6 -1
View File
@@ -255,6 +255,7 @@ static int memory_block_online(struct memory_block *mem)
nr_vmemmap_pages = mem->altmap->free;
mem_hotplug_begin();
clear_zone_contiguous(zone);
if (nr_vmemmap_pages) {
ret = mhp_init_memmap_on_memory(start_pfn, nr_vmemmap_pages, zone);
if (ret)
@@ -279,6 +280,7 @@ static int memory_block_online(struct memory_block *mem)
mem->zone = zone;
out:
set_zone_contiguous(zone);
mem_hotplug_done();
return ret;
}
@@ -304,6 +306,7 @@ static int memory_block_offline(struct memory_block *mem)
nr_vmemmap_pages = mem->altmap->free;
mem_hotplug_begin();
clear_zone_contiguous(mem->zone);
if (nr_vmemmap_pages)
adjust_present_page_count(pfn_to_page(start_pfn), mem->group,
-nr_vmemmap_pages);
@@ -321,8 +324,10 @@ static int memory_block_offline(struct memory_block *mem)
if (nr_vmemmap_pages)
mhp_deinit_memmap_on_memory(start_pfn, nr_vmemmap_pages);
mem->zone = NULL;
out:
set_zone_contiguous(mem->zone);
if (!ret)
mem->zone = NULL;
mem_hotplug_done();
return ret;
}
+69
View File
@@ -1019,6 +1019,42 @@ struct zone {
* cma pages is present pages that are assigned for CMA use
* (MIGRATE_CMA).
*
* pages_with_online_memmap tracks pages within the zone that have
* an online memory map: present pages and memory holes whose
* memory map has been initialized and pfn_to_online_page()
* succeeds. When spanned_pages == pages_with_online_memmap,
* pfn_to_page() can be performed without further checks on any
* PFN within the zone span.
*
* Note: this counter may temporarily undercount when pages with an
* online memory map exist outside the current zone span. Such pages
* are only created during boot, when initializing the memory map of
* pages that do not fall into any zone span. The undercount itself
* can only happen after boot, during memory hotplug, when growing
* the zone to cover such pages and later shrinking it back, which
* may result in a "too small" value. This is safe: it merely
* prevents detecting a contiguous zone.
*
* Here is an example (page numbers are just for illustration
* purposes):
* after boot:
* [ zone span ]
* [ zone pages ]
* spanned=10, initialized=15, online=10
* online == spanned -> contiguous
*
* growing after hotplug (hotplug 5):
* [ zone span ]
* [ zone pages ] [ zone pages ]
* spanned=30, initialized=20, online=15
* online != spanned -> not contiguous
*
* shrinking after hotunplug (hotunplug 5 again):
* [ zone span ]
* [ zone pages ]
* spanned=15, initialized=15, online=10
* online != spanned -> not contiguous although contiguous
*
* So present_pages may be used by memory hotplug or memory power
* management logic to figure out unmanaged pages by checking
* (present_pages - managed_pages). And managed_pages should be used
@@ -1043,6 +1079,7 @@ struct zone {
atomic_long_t managed_pages;
unsigned long spanned_pages;
unsigned long present_pages;
unsigned long pages_with_online_memmap;
#if defined(CONFIG_MEMORY_HOTPLUG)
unsigned long present_early_pages;
#endif
@@ -1670,6 +1707,38 @@ static inline bool zone_is_zone_device(const struct zone *zone)
}
#endif
/**
* zone_is_contiguous - test whether a zone is contiguous
* @zone: the zone to test.
*
* In a contiguous zone, it is valid to call pfn_to_page() on any PFN in the
* spanned zone without requiring pfn_valid() or pfn_to_online_page() checks.
*
* Note that missing synchronization with memory offlining makes any PFN
* traversal prone to races.
*
* ZONE_DEVICE zones are always marked non-contiguous.
*
* Return: true if contiguous, otherwise false.
*/
static inline bool zone_is_contiguous(const struct zone *zone)
{
return READ_ONCE(zone->contiguous);
}
static inline void set_zone_contiguous(struct zone *zone)
{
if (zone_is_zone_device(zone))
return;
if (zone->spanned_pages == zone->pages_with_online_memmap)
WRITE_ONCE(zone->contiguous, true);
}
static inline void clear_zone_contiguous(struct zone *zone)
{
WRITE_ONCE(zone->contiguous, false);
}
/*
* Returns true if a zone has pages managed by the buddy allocator.
* All the reclaim decisions have to use this function rather than
+14 -3
View File
@@ -1983,6 +1983,7 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,
struct vm_area_struct *vma;
bool must_unlock = false;
vm_flags_t vm_flags;
int ret, err = -EFAULT;
long i;
if (!nr_pages)
@@ -1999,6 +2000,10 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,
*locked = 1;
}
/* See the MMU variant: support the traditional behavior. */
if (pages && !(foll_flags & FOLL_PIN))
foll_flags |= FOLL_GET;
/* calculate required read or write permissions.
* If FOLL_FORCE is set, we only require the "MAY" flags.
*/
@@ -2019,8 +2024,14 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,
if (pages) {
pages[i] = virt_to_page((void *)start);
if (pages[i])
get_page(pages[i]);
if (!pages[i])
break;
ret = try_grab_folio(page_folio(pages[i]), 1, foll_flags);
if (unlikely(ret)) {
pages[i] = NULL;
err = ret;
break;
}
}
start = (start + PAGE_SIZE) & PAGE_MASK;
@@ -2031,7 +2042,7 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,
*locked = 0;
}
return i ? : -EFAULT;
return i ? : err;
}
#endif /* !CONFIG_MMU */
+32 -47
View File
@@ -385,6 +385,7 @@ EXPORT_SYMBOL_GPL(pfn_to_online_page);
int __add_pages(int nid, unsigned long pfn, unsigned long nr_pages,
struct mhp_params *params)
{
const unsigned long start_pfn = pfn;
const unsigned long end_pfn = pfn + nr_pages;
unsigned long cur_nr_pages;
int err;
@@ -418,57 +419,50 @@ int __add_pages(int nid, unsigned long pfn, unsigned long nr_pages,
SECTION_ALIGN_UP(pfn + 1) - pfn);
err = sparse_add_section(nid, pfn, cur_nr_pages, altmap,
params->pgmap);
if (err)
if (err) {
__remove_pages(start_pfn, pfn - start_pfn, altmap,
params->pgmap);
break;
}
cond_resched();
}
vmemmap_populate_print_last();
return err;
}
static bool subsection_overlaps_zone(unsigned long pfn, struct zone *zone)
{
const unsigned long start_pfn = ALIGN_DOWN(pfn, PAGES_PER_SUBSECTION);
const unsigned long end_pfn = start_pfn + PAGES_PER_SUBSECTION - 1;
/* All pages in a subsection are either online or offline. */
if (unlikely(!pfn_to_online_page(start_pfn)))
return false;
/* Checking start+end is sufficient. */
return zone == page_zone(pfn_to_page(start_pfn)) ||
zone == page_zone(pfn_to_page(end_pfn));
}
/* find the smallest valid pfn in the range [start_pfn, end_pfn) */
static unsigned long find_smallest_section_pfn(int nid, struct zone *zone,
unsigned long start_pfn,
unsigned long end_pfn)
static unsigned long find_smallest_section_pfn(struct zone *zone,
unsigned long start_pfn, unsigned long end_pfn)
{
for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SUBSECTION) {
if (unlikely(!pfn_to_online_page(start_pfn)))
continue;
if (unlikely(pfn_to_nid(start_pfn) != nid))
continue;
if (zone != page_zone(pfn_to_page(start_pfn)))
continue;
return start_pfn;
if (subsection_overlaps_zone(start_pfn, zone))
return start_pfn;
}
return 0;
}
/* find the biggest valid pfn in the range [start_pfn, end_pfn). */
static unsigned long find_biggest_section_pfn(int nid, struct zone *zone,
unsigned long start_pfn,
unsigned long end_pfn)
static unsigned long find_biggest_section_pfn(struct zone *zone,
unsigned long start_pfn, unsigned long end_pfn)
{
unsigned long pfn;
/* pfn is the end pfn of a memory section. */
pfn = end_pfn - 1;
for (; pfn >= start_pfn; pfn -= PAGES_PER_SUBSECTION) {
if (unlikely(!pfn_to_online_page(pfn)))
continue;
if (unlikely(pfn_to_nid(pfn) != nid))
continue;
if (zone != page_zone(pfn_to_page(pfn)))
continue;
return pfn;
for (; end_pfn > start_pfn; end_pfn -= PAGES_PER_SUBSECTION) {
if (subsection_overlaps_zone(end_pfn - 1, zone))
return end_pfn - 1;
}
return 0;
}
@@ -476,7 +470,6 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
unsigned long end_pfn)
{
unsigned long pfn;
int nid = zone_to_nid(zone);
if (zone->zone_start_pfn == start_pfn) {
/*
@@ -485,7 +478,7 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
* In this case, we find second smallest valid mem_section
* for shrinking zone.
*/
pfn = find_smallest_section_pfn(nid, zone, end_pfn,
pfn = find_smallest_section_pfn(zone, end_pfn,
zone_end_pfn(zone));
if (pfn) {
zone->spanned_pages = zone_end_pfn(zone) - pfn;
@@ -501,7 +494,7 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
* In this case, we find second biggest valid mem_section for
* shrinking zone.
*/
pfn = find_biggest_section_pfn(nid, zone, zone->zone_start_pfn,
pfn = find_biggest_section_pfn(zone, zone->zone_start_pfn,
start_pfn);
if (pfn)
zone->spanned_pages = pfn - zone->zone_start_pfn + 1;
@@ -562,18 +555,13 @@ void remove_pfn_range_from_zone(struct zone *zone,
/*
* Zone shrinking code cannot properly deal with ZONE_DEVICE. So
* we will not try to shrink the zones - which is okay as
* set_zone_contiguous() cannot deal with ZONE_DEVICE either way.
* we will not try to shrink it.
*/
if (zone_is_zone_device(zone))
return;
clear_zone_contiguous(zone);
shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
update_pgdat_span(pgdat);
set_zone_contiguous(zone);
}
/**
@@ -751,8 +739,6 @@ void move_pfn_range_to_zone(struct zone *zone, unsigned long start_pfn,
struct pglist_data *pgdat = zone->zone_pgdat;
int nid = pgdat->node_id;
clear_zone_contiguous(zone);
if (zone_is_empty(zone))
init_currently_empty_zone(zone, start_pfn, nr_pages);
resize_zone_range(zone, start_pfn, nr_pages);
@@ -780,8 +766,6 @@ void move_pfn_range_to_zone(struct zone *zone, unsigned long start_pfn,
memmap_init_range(nr_pages, nid, zone_idx(zone), start_pfn, 0,
MEMINIT_HOTPLUG, altmap, migratetype,
isolate_pageblock);
set_zone_contiguous(zone);
}
struct auto_movable_stats {
@@ -1077,6 +1061,7 @@ void adjust_present_page_count(struct page *page, struct memory_group *group,
if (early_section(__pfn_to_section(page_to_pfn(page))))
zone->present_early_pages += nr_pages;
zone->present_pages += nr_pages;
zone->pages_with_online_memmap += nr_pages;
zone->zone_pgdat->node_present_pages += nr_pages;
if (group && movable)
+36 -28
View File
@@ -823,23 +823,41 @@ void __meminit init_deferred_page(unsigned long pfn, int nid)
* the zone boundary; the pages in such holes will be prepended to the
* zone/node above the hole except for the trailing pages in the last
* section that will be appended to the zone/node below.
*
* The function counts pages that should be added to
* zone->pages_with_online_memmap. With CONFIG_SPARSEMEM_VMEMMAP there could
* be offline subsections even though the entire memory map is valid and all
* the early sections are online. Count only pages in online subsections
* (see pfn_to_online_page()).
*/
static void __init init_unavailable_range(unsigned long spfn,
unsigned long epfn,
int zone, int node)
static unsigned long __init init_unavailable_range(unsigned long spfn,
unsigned long epfn,
int zone, int node)
{
unsigned long next_chunk_pfn __maybe_unused = spfn;
unsigned long pfn;
u64 pgcnt = 0;
u64 online_pgcnt = 0, pgcnt = 0;
bool is_online = true;
for_each_valid_pfn(pfn, spfn, epfn) {
__init_single_page(pfn_to_page(pfn), pfn, zone, node);
__SetPageReserved(pfn_to_page(pfn));
pgcnt++;
#ifdef CONFIG_SPARSEMEM_VMEMMAP
if (pfn >= next_chunk_pfn) {
is_online = pfn_section_valid(__pfn_to_section(pfn), pfn);
next_chunk_pfn = min(SUBSECTION_ALIGN_UP(pfn + 1), epfn);
}
#endif
if (is_online)
online_pgcnt++;
}
if (pgcnt)
pr_info("On node %d, zone %s: %lld pages in unavailable ranges\n",
node, zone_names[zone], pgcnt);
return online_pgcnt;
}
/*
@@ -942,9 +960,21 @@ static void __init memmap_init_zone_range(struct zone *zone,
memmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn,
zone_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE,
false);
zone->pages_with_online_memmap += end_pfn - start_pfn;
if (*hole_pfn < start_pfn)
init_unavailable_range(*hole_pfn, start_pfn, zone_id, nid);
if (*hole_pfn < start_pfn) {
unsigned long hole_start_pfn = *hole_pfn;
unsigned long pgcnt;
if (hole_start_pfn < zone_start_pfn) {
init_unavailable_range(hole_start_pfn, zone_start_pfn,
zone_id, nid);
hole_start_pfn = zone_start_pfn;
}
pgcnt = init_unavailable_range(hole_start_pfn, start_pfn,
zone_id, nid);
zone->pages_with_online_memmap += pgcnt;
}
*hole_pfn = end_pfn;
}
@@ -2236,28 +2266,6 @@ void __init init_cma_pageblock(struct page *page)
}
#endif
void set_zone_contiguous(struct zone *zone)
{
unsigned long block_start_pfn = zone->zone_start_pfn;
unsigned long block_end_pfn;
block_end_pfn = pageblock_end_pfn(block_start_pfn);
for (; block_start_pfn < zone_end_pfn(zone);
block_start_pfn = block_end_pfn,
block_end_pfn += pageblock_nr_pages) {
block_end_pfn = min(block_end_pfn, zone_end_pfn(zone));
if (!__pageblock_pfn_to_page(block_start_pfn,
block_end_pfn, zone))
return;
cond_resched();
}
/* We confirm that there is no hole */
zone->contiguous = true;
}
/*
* Check if a PFN range intersects multiple zones on one or more
* NUMA nodes. Specify the @nid argument if it is known that this
-6
View File
@@ -20,15 +20,9 @@ struct vmem_altmap;
/* perform sanity checks on struct pages being allocated or freed */
DECLARE_STATIC_KEY_MAYBE(CONFIG_DEBUG_VM, check_pages_enabled);
void set_zone_contiguous(struct zone *zone);
bool pfn_range_intersects_zones(int nid, unsigned long start_pfn,
unsigned long nr_pages);
static inline void clear_zone_contiguous(struct zone *zone)
{
zone->contiguous = false;
}
void memblock_free_pages(unsigned long pfn, unsigned int order);
void *memmap_alloc(phys_addr_t size, phys_addr_t align, phys_addr_t min_addr,
+1 -1
View File
@@ -214,7 +214,7 @@ extern struct page *__pageblock_pfn_to_page(unsigned long start_pfn,
static inline struct page *pageblock_pfn_to_page(unsigned long start_pfn,
unsigned long end_pfn, struct zone *zone)
{
if (zone->contiguous)
if (zone_is_contiguous(zone))
return pfn_to_page(start_pfn);
return __pageblock_pfn_to_page(start_pfn, end_pfn, zone);