mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
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:
commit
2ee2475e15
8 files changed
+164
-86
No files matched your search
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user