summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-11-10 08:58:21 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-11-10 08:58:21 -0800
commit067c5c24cef23d913b67eaaf71ae67e7169a6620 (patch)
tree64f184aa4a8332109b2578a8a83c98a35848b0cc
parentf7c68e9d5d050301548d93ccbccd644151ced269 (diff)
parent2122887b04f3f2d76daa80055c2c273926032fa0 (diff)
downloadlwn-master.tar.gz
lwn-master.zip
Merge tag 'page_alloc-for-7.4-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/mm/page_allocHEADmaster
Pull page allocator fixes from Vlastimil Babka: - Stable fixes for various races of page allocator's reading of zonelists and memory hotplug activity. These can result in BUGs, allocation failures or allocations from wrong nodes (Gregory Price, Josef Bacik) - Stable fixes for bugs free_pages_nolock(), page_frag allocation, page isolation, that can trigger WARNs, degraded operation or infinite loop (Josef Bacik) * tag 'page_alloc-for-7.4-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/mm/page_alloc: mm/page_alloc: remove the zone_spans_pfn() check from get_pfnblock_bitmap_bitidx() mm/memory_hotplug: resize zones under their seqlock mm: page_isolation: do not take slab pages for free mm: page_frag: refill the cache for large fragments mm/page_alloc: clear ->private of deferred free pages mm/page_alloc: don't undo another isolation in alloc_contig_range mm/mmzone: remove zonelist_node_idx() mm/mempolicy: read the zoneref slot once in mempolicy_slab_node() mm/page_alloc: retry alloc_contig_pages() when the zonelist is rebuilt mm/hugetlb: retry the pool walk when the zonelist is rebuilt mm/page_alloc: retry allocations that raced with a zonelist rebuild mm/mmzone: read each zoneref slot once when filtering by nodemask mm/page_alloc: snapshot the preferred zone against shrink rebuilds mm/mmzone: read the zoneref slot once in zonelist_zone() mm/page_alloc: detect zonelist rebuilds from memory hot-add mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone mm/page_alloc: keep the pagesets of an empty zone mm/page_alloc: reset can_retry_reserves when the slowpath restarts
-rw-r--r--include/linux/mmzone.h9
-rw-r--r--include/trace/events/oom.h8
-rw-r--r--mm/hugetlb.c13
-rw-r--r--mm/memory_hotplug.c51
-rw-r--r--mm/mempolicy.c6
-rw-r--r--mm/mmzone.c11
-rw-r--r--mm/page_alloc.c116
-rw-r--r--mm/page_alloc.h19
-rw-r--r--mm/page_frag_cache.c21
-rw-r--r--mm/page_isolation.c7
10 files changed, 185 insertions, 76 deletions
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index ecd1ebd49756..72ffb2959c4f 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1439,7 +1439,6 @@ struct zoneref {
*
* zonelist_zone() - Return the struct zone * for an entry in _zonerefs
* zonelist_zone_idx() - Return the index of the zone for an entry
- * zonelist_node_idx() - Return the index of the node for an entry
*/
struct zonelist {
struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
@@ -1855,7 +1854,8 @@ extern struct zone *next_zone(struct zone *zone);
static inline struct zone *zonelist_zone(struct zoneref *zoneref)
{
- return zoneref->zone;
+ /* Memory hotplug rewrites zonelists in place. */
+ return READ_ONCE(zoneref->zone);
}
static inline int zonelist_zone_idx(const struct zoneref *zoneref)
@@ -1863,11 +1863,6 @@ static inline int zonelist_zone_idx(const struct zoneref *zoneref)
return zoneref->zone_idx;
}
-static inline int zonelist_node_idx(const struct zoneref *zoneref)
-{
- return zone_to_nid(zoneref->zone);
-}
-
struct zoneref *__next_zones_zonelist(struct zoneref *z,
enum zone_type highest_zoneidx,
const nodemask_t *nodes);
diff --git a/include/trace/events/oom.h b/include/trace/events/oom.h
index 9f0a5d1482c4..cbcf6fe4cc61 100644
--- a/include/trace/events/oom.h
+++ b/include/trace/events/oom.h
@@ -33,7 +33,7 @@ TRACE_EVENT(oom_score_adj_update,
TRACE_EVENT(reclaim_retry_zone,
- TP_PROTO(struct zoneref *zoneref,
+ TP_PROTO(struct zone *zone,
int order,
unsigned long reclaimable,
unsigned long available,
@@ -41,7 +41,7 @@ TRACE_EVENT(reclaim_retry_zone,
int no_progress_loops,
bool wmark_check),
- TP_ARGS(zoneref, order, reclaimable, available, min_wmark, no_progress_loops, wmark_check),
+ TP_ARGS(zone, order, reclaimable, available, min_wmark, no_progress_loops, wmark_check),
TP_STRUCT__entry(
__field( int, node)
@@ -55,8 +55,8 @@ TRACE_EVENT(reclaim_retry_zone,
),
TP_fast_assign(
- __entry->node = zonelist_node_idx(zoneref);
- __entry->zone_idx = zonelist_zone_idx(zoneref);
+ __entry->node = zone_to_nid(zone);
+ __entry->zone_idx = zone_idx(zone);
__entry->order = order;
__entry->reclaimable = reclaimable;
__entry->available = available;
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 630c96cd1e88..ce9c8e57b8e5 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1297,11 +1297,11 @@ static struct folio *dequeue_hugetlb_folio_node_exact(struct hstate *h,
static struct folio *dequeue_hugetlb_folio_nodemask(struct hstate *h, gfp_t gfp_mask,
int nid, nodemask_t *nmask)
{
- unsigned int cpuset_mems_cookie;
+ unsigned int cpuset_mems_cookie, zonelist_cookie;
struct zonelist *zonelist;
struct zone *zone;
struct zoneref *z;
- int node = NUMA_NO_NODE;
+ int node;
/* 'nid' should not be NUMA_NO_NODE. Try to catch any misuse of it and rectifiy. */
if (nid == NUMA_NO_NODE)
@@ -1310,10 +1310,16 @@ static struct folio *dequeue_hugetlb_folio_nodemask(struct hstate *h, gfp_t gfp_
zonelist = node_zonelist(nid, gfp_mask);
retry_cpuset:
+ node = NUMA_NO_NODE;
cpuset_mems_cookie = read_mems_allowed_begin();
+ zonelist_cookie = zonelist_iter_begin();
for_each_zone_zonelist_nodemask(zone, z, zonelist, gfp_zone(gfp_mask), nmask) {
struct folio *folio;
+ /* A rebuild may have shifted a node outside @nmask into this slot. */
+ if (unlikely(check_retry_zonelist(zonelist_cookie)))
+ goto retry_cpuset;
+
if (!cpuset_zone_allowed(zone, gfp_mask))
continue;
/*
@@ -1328,7 +1334,8 @@ retry_cpuset:
if (folio)
return folio;
}
- if (unlikely(read_mems_allowed_retry(cpuset_mems_cookie)))
+ if (unlikely(read_mems_allowed_retry(cpuset_mems_cookie) ||
+ check_retry_zonelist(zonelist_cookie)))
goto retry_cpuset;
return NULL;
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index c9cc42e54f05..b9f02bc6ed0d 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -461,6 +461,22 @@ static unsigned long find_biggest_section_pfn(struct zone *zone,
return 0;
}
+/*
+ * The page allocator checks pages against the span of their zone while the
+ * zone is resized, see page_outside_zone_boundaries(). It must not see the
+ * new value of one field together with the old value of the other one.
+ */
+static void zone_set_span(struct zone *zone, unsigned long start_pfn,
+ unsigned long spanned_pages)
+{
+ unsigned long flags;
+
+ write_seqlock_irqsave(&zone->span_seqlock, flags);
+ zone->zone_start_pfn = start_pfn;
+ zone->spanned_pages = spanned_pages;
+ write_sequnlock_irqrestore(&zone->span_seqlock, flags);
+}
+
static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
unsigned long end_pfn)
{
@@ -475,13 +491,10 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
*/
pfn = find_smallest_section_pfn(zone, end_pfn,
zone_end_pfn(zone));
- if (pfn) {
- zone->spanned_pages = zone_end_pfn(zone) - pfn;
- zone->zone_start_pfn = pfn;
- } else {
- zone->zone_start_pfn = 0;
- zone->spanned_pages = 0;
- }
+ if (pfn)
+ zone_set_span(zone, pfn, zone_end_pfn(zone) - pfn);
+ else
+ zone_set_span(zone, 0, 0);
} else if (zone_end_pfn(zone) == end_pfn) {
/*
* If the section is biggest section in the zone, it need
@@ -492,11 +505,10 @@ static void shrink_zone_span(struct zone *zone, unsigned long 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;
- else {
- zone->zone_start_pfn = 0;
- zone->spanned_pages = 0;
- }
+ zone_set_span(zone, zone->zone_start_pfn,
+ pfn - zone->zone_start_pfn + 1);
+ else
+ zone_set_span(zone, 0, 0);
}
}
@@ -684,12 +696,12 @@ static void online_pages_range(unsigned long start_pfn, unsigned long nr_pages)
static void __meminit resize_zone_range(struct zone *zone, unsigned long start_pfn,
unsigned long nr_pages)
{
- unsigned long old_end_pfn = zone_end_pfn(zone);
+ unsigned long end_pfn = max(start_pfn + nr_pages, zone_end_pfn(zone));
- if (zone_is_empty(zone) || start_pfn < zone->zone_start_pfn)
- zone->zone_start_pfn = start_pfn;
+ if (!zone_is_empty(zone) && start_pfn > zone->zone_start_pfn)
+ start_pfn = zone->zone_start_pfn;
- zone->spanned_pages = max(start_pfn + nr_pages, old_end_pfn) - zone->zone_start_pfn;
+ zone_set_span(zone, start_pfn, end_pfn - start_pfn);
}
static void __meminit resize_pgdat_range(struct pglist_data *pgdat, unsigned long start_pfn,
@@ -1832,6 +1844,13 @@ static int scan_movable_pages(unsigned long start, unsigned long end,
if (PageOffline(page) && page_count(page))
return -EBUSY;
+ /*
+ * A page that was allocated, but was not a slab page yet, when
+ * its pageblock was isolated.
+ */
+ if (PageSlab(page))
+ return -EBUSY;
+
folio = page_folio(page);
if (!folio_test_hugetlb(folio))
continue;
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 0cb6da1c47a4..c7d4d149c9b8 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -2176,6 +2176,7 @@ unsigned int mempolicy_slab_node(void)
case MPOL_PREFERRED_MANY:
{
struct zoneref *z;
+ struct zone *zone;
/*
* Follow bind policy behavior and start allocation at the
@@ -2186,7 +2187,8 @@ unsigned int mempolicy_slab_node(void)
zonelist = &NODE_DATA(node)->node_zonelists[ZONELIST_FALLBACK];
z = first_zones_zonelist(zonelist, highest_zoneidx,
&policy->nodes);
- return zonelist_zone(z) ? zonelist_node_idx(z) : node;
+ zone = zonelist_zone(z);
+ return zone ? zone_to_nid(zone) : node;
}
case MPOL_LOCAL:
return node;
@@ -3085,7 +3087,7 @@ int mpol_misplaced(struct folio *folio, struct vm_fault *vmf,
node_zonelist(thisnid, GFP_HIGHUSER),
gfp_zone(GFP_HIGHUSER),
&pol->nodes);
- polnid = zonelist_node_idx(z);
+ polnid = zone_to_nid(zonelist_zone(z));
break;
default:
diff --git a/mm/mmzone.c b/mm/mmzone.c
index 9cc9ef588580..65880396f104 100644
--- a/mm/mmzone.c
+++ b/mm/mmzone.c
@@ -43,13 +43,14 @@ struct zone *next_zone(struct zone *zone)
return zone;
}
-static inline int zref_in_nodemask(struct zoneref *zref,
- const nodemask_t *nodes)
+/* A NULL zone terminates the zonelist, so it ends the search. */
+static inline bool zone_in_nodemask(const struct zone *zone,
+ const nodemask_t *nodes)
{
#ifdef CONFIG_NUMA
- return node_isset(zonelist_node_idx(zref), *nodes);
+ return !zone || node_isset(zone_to_nid(zone), *nodes);
#else
- return 1;
+ return true;
#endif /* CONFIG_NUMA */
}
@@ -67,7 +68,7 @@ struct zoneref *__next_zones_zonelist(struct zoneref *z,
z++;
else
while (zonelist_zone_idx(z) > highest_zoneidx ||
- (zonelist_zone(z) && !zref_in_nodemask(z, nodes)))
+ !zone_in_nodemask(zonelist_zone(z), nodes))
z++;
return z;
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index eb4f02617f53..6624293e4d78 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -370,7 +370,6 @@ get_pfnblock_bitmap_bitidx(const struct page *page, unsigned long pfn,
BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4);
#endif
BUILD_BUG_ON(__MIGRATE_TYPE_END > PAGEBLOCK_MIGRATETYPE_MASK);
- VM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn), page);
bitmap = get_pageblock_bitmap(page, pfn);
*bitidx = pfn_to_bitidx(page, pfn);
@@ -1562,6 +1561,8 @@ static void free_one_page(struct zone *zone, struct page *page,
llist_for_each_entry_safe(p, tmp, llnode, pcp_llist) {
unsigned int p_order = p->private;
+ /* It may end up as a tail of the merged free page. */
+ p->private = 0;
split_large_buddy(zone, p, page_to_pfn(p), p_order, fpi_flags);
__count_vm_events(PGFREE, 1 << p_order);
}
@@ -3799,12 +3800,21 @@ get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
{
struct zoneref *z;
struct zone *zone;
+ struct zone *preferred_zone;
struct pglist_data *last_pgdat = NULL;
bool last_pgdat_dirty_ok = false;
bool no_fallback;
bool skip_kswapd_nodes = nr_online_nodes > 1;
bool skipped_kswapd_nodes = false;
+ /*
+ * A concurrent zonelist rebuild can NULL or replace this slot. Zones
+ * are never freed, so reading it once keeps a usable zone.
+ */
+ preferred_zone = zonelist_zone(ac->preferred_zoneref);
+ if (!preferred_zone)
+ return NULL;
+
retry:
/*
* Scan zonelist, looking for a zone with enough free.
@@ -3851,7 +3861,7 @@ retry:
}
if (no_fallback && !defrag_mode && nr_online_nodes > 1 &&
- zone != zonelist_zone(ac->preferred_zoneref)) {
+ zone != preferred_zone) {
int local_nid;
/*
@@ -3859,7 +3869,7 @@ retry:
* fragmenting fallbacks. Locality is more important
* than fragmentation avoidance.
*/
- local_nid = zonelist_node_idx(ac->preferred_zoneref);
+ local_nid = zone_to_nid(preferred_zone);
if (zone_to_nid(zone) != local_nid) {
alloc_flags &= ~ALLOC_NOFRAGMENT;
goto retry;
@@ -3921,7 +3931,7 @@ check_alloc_wmark:
goto try_this_zone;
if (!node_reclaim_enabled() ||
- !zone_allows_reclaim(zonelist_zone(ac->preferred_zoneref), zone))
+ !zone_allows_reclaim(preferred_zone, zone))
continue;
if (!node_reclaim(zone->zone_pgdat, gfp_mask, order))
@@ -3934,7 +3944,7 @@ check_alloc_wmark:
}
try_this_zone:
- page = rmqueue(zonelist_zone(ac->preferred_zoneref), zone, order,
+ page = rmqueue(preferred_zone, zone, order,
gfp_mask, alloc_flags, ac->migratetype);
if (page) {
prep_new_page(page, order, gfp_mask, alloc_flags);
@@ -4405,24 +4415,21 @@ EXPORT_SYMBOL_GPL(fs_reclaim_release);
* retries the allocation if zonelist changes. Writer side is protected by the
* embedded spin_lock.
*/
-static DEFINE_SEQLOCK(zonelist_update_seq);
+DEFINE_SEQLOCK(zonelist_update_seq);
-static unsigned int zonelist_iter_begin(void)
+/*
+ * Never wait for the writer, which an ALLOC_NOLOCK caller in NMI may have
+ * interrupted. Read the raw seqcount: on PREEMPT_RT the seqlock accessor takes
+ * the spinlock while a write is in progress.
+ */
+static unsigned int zonelist_iter_sample(void)
{
- if (IS_ENABLED(CONFIG_MEMORY_HOTREMOVE))
- return read_seqbegin(&zonelist_update_seq);
+ if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
+ return raw_seqcount_begin(&zonelist_update_seq.seqcount.seqcount);
return 0;
}
-static unsigned int check_retry_zonelist(unsigned int seq)
-{
- if (IS_ENABLED(CONFIG_MEMORY_HOTREMOVE))
- return read_seqretry(&zonelist_update_seq, seq);
-
- return seq;
-}
-
/* Perform direct synchronous page reclaim */
static unsigned long
__perform_reclaim(gfp_t gfp_mask, unsigned int order,
@@ -4675,7 +4682,7 @@ should_reclaim_retry(gfp_t gfp_mask, unsigned order,
*/
wmark = __zone_watermark_ok(zone, order, min_wmark,
ac->highest_zoneidx, alloc_flags, available);
- trace_reclaim_retry_zone(z, order, reclaimable,
+ trace_reclaim_retry_zone(zone, order, reclaimable,
available, min_wmark, *no_progress_loops, wmark);
if (wmark) {
ret = true;
@@ -4788,7 +4795,7 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
unsigned int zonelist_iter_cookie;
int reserve_flags;
bool compact_first = false;
- bool can_retry_reserves = true;
+ bool can_retry_reserves;
unsigned long alloc_start_time = jiffies;
/*
@@ -4822,6 +4829,7 @@ restart:
no_progress_loops = 0;
compact_result = COMPACT_SKIPPED;
compact_priority = DEF_COMPACT_PRIORITY;
+ can_retry_reserves = true;
cpuset_mems_cookie = read_mems_allowed_begin();
zonelist_iter_cookie = zonelist_iter_begin();
@@ -5110,6 +5118,12 @@ fail:
warn_alloc(gfp_mask, ac->nodemask,
"page allocation failure: order:%u", order);
got_pg:
+ /* A rebuild during the walk may have handed out an ineligible zone. */
+ if (page && check_retry_zonelist(zonelist_iter_cookie)) {
+ __free_frozen_pages(page, order, FPI_NONE);
+ page = NULL;
+ goto restart;
+ }
return page;
}
@@ -5188,11 +5202,13 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
{
struct page *page;
struct zone *zone;
+ struct zone *preferred_zone;
struct zoneref *z;
struct per_cpu_pages *pcp;
struct list_head *pcp_list;
struct alloc_context ac;
unsigned int alloc_flags = ALLOC_WMARK_LOW;
+ unsigned int zonelist_iter_cookie;
int nr_populated = 0, nr_account = 0;
/*
@@ -5233,9 +5249,15 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
/* May set ALLOC_NOFRAGMENT, fragmentation will return 1 page. */
gfp &= gfp_allowed_mask;
gfp = current_gfp_context(gfp);
+ zonelist_iter_cookie = zonelist_iter_sample();
if (!prepare_alloc_pages(gfp, 0, preferred_nid, nodemask, &ac, &gfp, &alloc_flags))
goto out;
+ /* Read once; see get_page_from_freelist(). */
+ preferred_zone = zonelist_zone(ac.preferred_zoneref);
+ if (!preferred_zone)
+ goto failed;
+
/* Find an allowed local zone that meets the low watermark. */
z = ac.preferred_zoneref;
for_next_zone_zonelist_nodemask(zone, z, ac.highest_zoneidx, ac.nodemask) {
@@ -5246,8 +5268,8 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
continue;
}
- if (nr_online_nodes > 1 && zone != zonelist_zone(ac.preferred_zoneref) &&
- zone_to_nid(zone) != zonelist_node_idx(ac.preferred_zoneref)) {
+ if (nr_online_nodes > 1 && zone != preferred_zone &&
+ zone_to_nid(zone) != zone_to_nid(preferred_zone)) {
goto failed;
}
@@ -5255,7 +5277,7 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
retry_this_zone:
mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK) + nr_pages - nr_populated;
if (zone_watermark_fast(zone, 0, mark,
- zonelist_zone_idx(ac.preferred_zoneref),
+ zone_idx(preferred_zone),
alloc_flags, gfp)) {
break;
}
@@ -5277,6 +5299,10 @@ retry_this_zone:
if (unlikely(!zone))
goto failed;
+ /* The zone was chosen from a zonelist that a rebuild may have changed. */
+ if (unlikely(check_retry_zonelist(zonelist_iter_cookie)))
+ goto failed;
+
/* spin_trylock may fail due to a parallel drain or IRQ reentrancy. */
pcp = pcp_spin_trylock(zone->per_cpu_pageset);
if (!pcp)
@@ -5314,7 +5340,7 @@ retry_this_zone:
pcp_spin_unlock(pcp);
__count_zid_vm_events(PGALLOC, zone_idx(zone), nr_account);
- zone_statistics(zonelist_zone(ac.preferred_zoneref), zone, nr_account);
+ zone_statistics(preferred_zone, zone, nr_account);
out:
return nr_populated;
@@ -5417,6 +5443,7 @@ struct page *__alloc_frozen_pages_noprof(gfp_t gfp, unsigned int order,
.alloc_flags = alloc_flags,
};
unsigned int fastpath_alloc_flags = alloc_flags;
+ unsigned int zonelist_iter_cookie;
/* Other flags could be supported later if needed. */
if (WARN_ON(alloc_flags & ~(ALLOC_NOLOCK | ALLOC_NO_CODETAG)))
@@ -5446,6 +5473,7 @@ struct page *__alloc_frozen_pages_noprof(gfp_t gfp, unsigned int order,
*/
gfp = current_gfp_context(gfp);
alloc_gfp = gfp;
+ zonelist_iter_cookie = zonelist_iter_sample();
if (!prepare_alloc_pages(gfp, order, preferred_nid, nodemask, &ac,
&alloc_gfp, &fastpath_alloc_flags))
return NULL;
@@ -5462,6 +5490,11 @@ struct page *__alloc_frozen_pages_noprof(gfp_t gfp, unsigned int order,
/* First allocation attempt (or, for nolock, only attempt) */
page = get_page_from_freelist(alloc_gfp, order, fastpath_alloc_flags, &ac);
+ if (likely(page) && unlikely(check_retry_zonelist(zonelist_iter_cookie))) {
+ __free_frozen_pages(page, order,
+ alloc_flags & ALLOC_NOLOCK ? FPI_NOLOCK : 0);
+ page = NULL;
+ }
if (likely(page) || (alloc_flags & ALLOC_NOLOCK))
goto out;
@@ -5961,7 +5994,7 @@ int local_memory_node(int node)
z = first_zones_zonelist(node_zonelist(node, GFP_KERNEL),
gfp_zone(GFP_KERNEL),
NULL);
- return zonelist_node_idx(z);
+ return zone_to_nid(zonelist_zone(z));
}
#endif
@@ -6327,6 +6360,10 @@ void __meminit setup_zone_pageset(struct zone *zone)
{
int cpu;
+ /* The zone has been populated before, see zone_pcp_reset(). */
+ if (zone->per_cpu_pageset != &boot_pageset)
+ return;
+
/* Size may be 0 on !SMP && !NUMA */
if (sizeof(struct per_cpu_zonestat) > 0)
zone->per_cpu_zonestats = alloc_percpu(struct per_cpu_zonestat);
@@ -6419,6 +6456,10 @@ void __init setup_per_cpu_pageset(void)
__meminit void zone_pcp_init(struct zone *zone)
{
+ /* This initialization only needs to be done once */
+ if (zone->per_cpu_pageset)
+ return;
+
/*
* per cpu subsystem is not up at this point. The following code
* relies on the ability of the linker to provide the
@@ -7344,9 +7385,13 @@ int alloc_contig_frozen_range_noprof(unsigned long start, unsigned long end,
* put back to page allocator so that buddy can use them.
*/
+ /*
+ * On failure this has undone its own isolation, and any other
+ * isolated blocks in the range belong to someone else, e.g. offline.
+ */
ret = start_isolate_page_range(start, end, mode);
if (ret)
- goto done;
+ return ret;
drain_all_pages(cc.zone);
@@ -7550,6 +7595,7 @@ struct page *alloc_contig_frozen_pages_noprof(unsigned long nr_pages,
gfp_t gfp_mask, int nid, nodemask_t *nodemask)
{
unsigned long ret, pfn, flags;
+ unsigned int zonelist_cookie;
struct zonelist *zonelist;
struct zone *zone;
struct zoneref *z;
@@ -7558,8 +7604,13 @@ struct page *alloc_contig_frozen_pages_noprof(unsigned long nr_pages,
retry:
zonelist = node_zonelist(nid, gfp_mask);
+ zonelist_cookie = zonelist_iter_begin();
for_each_zone_zonelist_nodemask(zone, z, zonelist,
gfp_zone(gfp_mask), nodemask) {
+ /* A rebuild may have shifted an ineligible zone into this slot. */
+ if (unlikely(check_retry_zonelist(zonelist_cookie)))
+ goto retry;
+
spin_lock_irqsave(&zone->lock, flags);
pfn = ALIGN(zone->zone_start_pfn, nr_pages);
@@ -7594,6 +7645,9 @@ retry:
* second pass, we will try moving HugeTLB pages when those pages are
* smaller than the requested contiguous region size.
*/
+ /* Redo a pass that a rebuild cut short before escalating. */
+ if (unlikely(check_retry_zonelist(zonelist_cookie)))
+ goto retry;
if (skip_hugetlb && skipped_hugetlb) {
skip_hugetlb = false;
goto retry;
@@ -7699,6 +7753,12 @@ void zone_pcp_enable(struct zone *zone)
mutex_unlock(&pcp_batch_high_lock);
}
+/*
+ * The zone has become empty. Its pagesets and zonestats are not freed: nothing
+ * keeps somebody who found the zone populated, such as refresh_cpu_vm_stats()
+ * or drain_all_pages(), from still using them. They are used again when the
+ * zone gets memory again.
+ */
void zone_pcp_reset(struct zone *zone)
{
int cpu;
@@ -7709,12 +7769,6 @@ void zone_pcp_reset(struct zone *zone)
pzstats = per_cpu_ptr(zone->per_cpu_zonestats, cpu);
drain_zonestat(zone, pzstats);
}
- free_percpu(zone->per_cpu_pageset);
- zone->per_cpu_pageset = &boot_pageset;
- if (zone->per_cpu_zonestats != &boot_zonestats) {
- free_percpu(zone->per_cpu_zonestats);
- zone->per_cpu_zonestats = &boot_zonestats;
- }
}
}
diff --git a/mm/page_alloc.h b/mm/page_alloc.h
index 7097ea1805f6..917653ae14a3 100644
--- a/mm/page_alloc.h
+++ b/mm/page_alloc.h
@@ -9,6 +9,7 @@
#include <linux/mm.h>
#include <linux/mmzone.h>
#include <linux/nodemask.h>
+#include <linux/seqlock.h>
#include <linux/types.h>
#define ALLOC_DEFAULT 0
@@ -267,6 +268,24 @@ extern void zone_pcp_disable(struct zone *zone);
extern void zone_pcp_enable(struct zone *zone);
extern void zone_pcp_init(struct zone *zone);
+extern seqlock_t zonelist_update_seq;
+
+static inline unsigned int zonelist_iter_begin(void)
+{
+ if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
+ return read_seqbegin(&zonelist_update_seq);
+
+ return 0;
+}
+
+static inline unsigned int check_retry_zonelist(unsigned int seq)
+{
+ if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
+ return read_seqretry(&zonelist_update_seq, seq);
+
+ return 0;
+}
+
enum fallback_result {
/* Found suitable migratetype, *mt_out is valid. */
FALLBACK_FOUND,
diff --git a/mm/page_frag_cache.c b/mm/page_frag_cache.c
index e63efe78b7d4..8914437ddc51 100644
--- a/mm/page_frag_cache.c
+++ b/mm/page_frag_cache.c
@@ -119,15 +119,20 @@ refill:
size = PAGE_SIZE << encoded_page_decode_order(encoded_page);
offset = __ALIGN_KERNEL_MASK(nc->offset, ~align_mask);
if (unlikely(offset + fragsz > size)) {
- if (unlikely(fragsz > PAGE_SIZE)) {
+ if (unlikely(fragsz > size)) {
/*
- * The caller is trying to allocate a fragment
- * with fragsz > PAGE_SIZE but the cache isn't big
- * enough to satisfy the request, this may
- * happen in low memory conditions.
- * We don't release the cache page because
- * it could make memory pressure worse
- * so we simply return NULL here.
+ * The caller is trying to allocate a fragment that's
+ * larger than the whole cache page.
+ *
+ * Either fragsz is larger than PAGE_FRAG_CACHE_MAX_SIZE
+ * so this cannot ever succeed.
+ *
+ * Or the refill has fallen back to order-0 due to a low
+ * memory condition. Then we don't want to release the
+ * cache page to attempt a new large-size refill because
+ * it could make memory pressure worse.
+ *
+ * So in both cases we simply return NULL here.
*/
return NULL;
}
diff --git a/mm/page_isolation.c b/mm/page_isolation.c
index 5aaad037384e..f4f0571607ab 100644
--- a/mm/page_isolation.c
+++ b/mm/page_isolation.c
@@ -75,6 +75,13 @@ bool page_is_unmovable(struct zone *zone, struct page *page,
}
/*
+ * Slab pages are frozen: do not take their reference count of zero
+ * for that of a free page.
+ */
+ if (PageSlab(page))
+ return true;
+
+ /*
* We can't use page_count without pin a page
* because another CPU can free compound page.
* This check already skips compound tails of THP