BACKPORT: mm: page_alloc: close migratetype race between freeing and stealing
There are three freeing paths that read the page's migratetype optimistically before grabbing the zone lock. When this races with block stealing, those pages go on the wrong freelist. The paths in question are: - when freeing >costly orders that aren't THP - when freeing pages to the buddy upon pcp lock contention - when freeing pages that are isolated - when freeing pages initially during boot - when freeing the remainder in alloc_pages_exact() - when "accepting" unaccepted VM host memory before first use - when freeing pages during unpoisoning None of these are so hot that they would need this optimization at the cost of hampering defrag efforts. Especially when contrasted with the fact that the most common buddy freeing path - free_pcppages_bulk - is checking the migratetype under the zone->lock just fine. In addition, isolated pages need to look up the migratetype under the lock anyway, which adds branches to the locked section, and results in a double lookup when the pages are in fact isolated. Move the lookups into the lock. Link: https://lkml.kernel.org/r/20240320180429.678181-8-hannes@cmpxchg.org Change-Id: I7daf15e9bb30971f68492f5193757f192852176f Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Reported-by: Vlastimil Babka <vbabka@suse.cz> Reviewed-by: Vlastimil Babka <vbabka@suse.cz> Tested-by: Baolin Wang <baolin.wang@linux.alibaba.com> Cc: David Hildenbrand <david@redhat.com> Cc: "Huang, Ying" <ying.huang@intel.com> Cc: Mel Gorman <mgorman@techsingularity.net> Cc: Zi Yan <ziy@nvidia.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> (cherry picked from commit 55612e80e722ac554cc5e80df05555b4f8d40c37) [ Get the migratetype outside of zone->lock in __free_pages_ok() to maintiain vendor hook parameters. Fix trivial conflicts in mm/page_alloc.s - Kalesh Singh ] Bug: 420771453 Signed-off-by: Kalesh Singh <kaleshsingh@google.com>
This commit is contained in:
committed by
Kalesh Singh
parent
1a3654f59a
commit
209c219a0f
@@ -1381,18 +1381,15 @@ static void free_pcppages_bulk(struct zone *zone, int count,
|
||||
spin_unlock_irqrestore(&zone->lock, flags);
|
||||
}
|
||||
|
||||
static void free_one_page(struct zone *zone,
|
||||
struct page *page, unsigned long pfn,
|
||||
unsigned int order,
|
||||
int migratetype, fpi_t fpi_flags)
|
||||
static void free_one_page(struct zone *zone, struct page *page,
|
||||
unsigned long pfn, unsigned int order,
|
||||
fpi_t fpi_flags)
|
||||
{
|
||||
unsigned long flags;
|
||||
int migratetype;
|
||||
|
||||
spin_lock_irqsave(&zone->lock, flags);
|
||||
if (unlikely(has_isolate_pageblock(zone) ||
|
||||
is_migrate_isolate(migratetype))) {
|
||||
migratetype = get_pfnblock_migratetype(page, pfn);
|
||||
}
|
||||
migratetype = get_pfnblock_migratetype(page, pfn);
|
||||
__free_one_page(page, pfn, zone, order, migratetype, fpi_flags);
|
||||
spin_unlock_irqrestore(&zone->lock, flags);
|
||||
}
|
||||
@@ -1400,7 +1397,6 @@ static void free_one_page(struct zone *zone,
|
||||
static void __free_pages_ok(struct page *page, unsigned int order,
|
||||
fpi_t fpi_flags)
|
||||
{
|
||||
unsigned long flags;
|
||||
int migratetype;
|
||||
unsigned long pfn = page_to_pfn(page);
|
||||
struct zone *zone = page_zone(page);
|
||||
@@ -1430,13 +1426,7 @@ skip_prepare:
|
||||
if (skip_free_unref_page)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&zone->lock, flags);
|
||||
if (unlikely(has_isolate_pageblock(zone) ||
|
||||
is_migrate_isolate(migratetype))) {
|
||||
migratetype = get_pfnblock_migratetype(page, pfn);
|
||||
}
|
||||
__free_one_page(page, pfn, zone, order, migratetype, fpi_flags);
|
||||
spin_unlock_irqrestore(&zone->lock, flags);
|
||||
free_one_page(zone, page, pfn, order, fpi_flags);
|
||||
|
||||
__count_vm_events(PGFREE, 1 << order);
|
||||
}
|
||||
@@ -2646,7 +2636,7 @@ void free_unref_page(struct page *page, unsigned int order)
|
||||
struct per_cpu_pages *pcp;
|
||||
struct zone *zone;
|
||||
unsigned long pfn = page_to_pfn(page);
|
||||
int migratetype, pcpmigratetype;
|
||||
int migratetype;
|
||||
bool skip_free_unref_page = false;
|
||||
|
||||
if (!free_pages_prepare(page, order, FPI_NONE))
|
||||
@@ -2660,29 +2650,29 @@ void free_unref_page(struct page *page, unsigned int order)
|
||||
* get those areas back if necessary. Otherwise, we may have to free
|
||||
* excessively into the page allocator
|
||||
*/
|
||||
migratetype = pcpmigratetype = get_pfnblock_migratetype(page, pfn);
|
||||
migratetype = get_pfnblock_migratetype(page, pfn);
|
||||
trace_android_vh_free_unref_page_bypass(page, order, migratetype, &skip_free_unref_page);
|
||||
if (skip_free_unref_page)
|
||||
return;
|
||||
if (unlikely(migratetype > MIGRATE_RECLAIMABLE)) {
|
||||
if (unlikely(is_migrate_isolate(migratetype))) {
|
||||
free_one_page(page_zone(page), page, pfn, order, migratetype, FPI_NONE);
|
||||
free_one_page(page_zone(page), page, pfn, order, FPI_NONE);
|
||||
return;
|
||||
}
|
||||
#ifdef CONFIG_CMA
|
||||
if (!cma_has_pcplist() || migratetype != MIGRATE_CMA)
|
||||
#endif
|
||||
pcpmigratetype = MIGRATE_MOVABLE;
|
||||
migratetype = MIGRATE_MOVABLE;
|
||||
}
|
||||
|
||||
zone = page_zone(page);
|
||||
pcp_trylock_prepare(UP_flags);
|
||||
pcp = pcp_spin_trylock(zone->per_cpu_pageset);
|
||||
if (pcp) {
|
||||
free_unref_page_commit(zone, pcp, page, pcpmigratetype, order);
|
||||
free_unref_page_commit(zone, pcp, page, migratetype, order);
|
||||
pcp_spin_unlock(pcp);
|
||||
} else {
|
||||
free_one_page(zone, page, pfn, order, migratetype, FPI_NONE);
|
||||
free_one_page(zone, page, pfn, order, FPI_NONE);
|
||||
}
|
||||
pcp_trylock_finish(UP_flags);
|
||||
}
|
||||
@@ -2712,12 +2702,8 @@ void free_unref_folios(struct folio_batch *folios)
|
||||
* allocator.
|
||||
*/
|
||||
if (!pcp_allowed_order(order)) {
|
||||
int migratetype;
|
||||
|
||||
migratetype = get_pfnblock_migratetype(&folio->page,
|
||||
pfn);
|
||||
free_one_page(folio_zone(folio), &folio->page, pfn,
|
||||
order, migratetype, FPI_NONE);
|
||||
free_one_page(folio_zone(folio), &folio->page,
|
||||
pfn, order, FPI_NONE);
|
||||
continue;
|
||||
}
|
||||
folio->private = (void *)(unsigned long)order;
|
||||
@@ -2753,7 +2739,7 @@ void free_unref_folios(struct folio_batch *folios)
|
||||
*/
|
||||
if (is_migrate_isolate(migratetype)) {
|
||||
free_one_page(zone, &folio->page, pfn,
|
||||
order, migratetype, FPI_NONE);
|
||||
order, FPI_NONE);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2766,7 +2752,7 @@ void free_unref_folios(struct folio_batch *folios)
|
||||
if (unlikely(!pcp)) {
|
||||
pcp_trylock_finish(UP_flags);
|
||||
free_one_page(zone, &folio->page, pfn,
|
||||
order, migratetype, FPI_NONE);
|
||||
order, FPI_NONE);
|
||||
continue;
|
||||
}
|
||||
locked_zone = zone;
|
||||
@@ -7041,13 +7027,14 @@ bool take_page_off_buddy(struct page *page)
|
||||
bool put_page_back_buddy(struct page *page)
|
||||
{
|
||||
struct zone *zone = page_zone(page);
|
||||
unsigned long pfn = page_to_pfn(page);
|
||||
unsigned long flags;
|
||||
int migratetype = get_pfnblock_migratetype(page, pfn);
|
||||
bool ret = false;
|
||||
|
||||
spin_lock_irqsave(&zone->lock, flags);
|
||||
if (put_page_testzero(page)) {
|
||||
unsigned long pfn = page_to_pfn(page);
|
||||
int migratetype = get_pfnblock_migratetype(page, pfn);
|
||||
|
||||
ClearPageHWPoisonTakenOff(page);
|
||||
__free_one_page(page, pfn, zone, 0, migratetype, FPI_NONE);
|
||||
if (TestClearPageHWPoison(page)) {
|
||||
|
Reference in New Issue
Block a user