Preserve PG_zeroed when two buddy pages merge in __free_one_page().
Set it on the merged page only if both buddies are known-zero.  A buddy is
known-zero if it has PG_zeroed set, or if it is reported and the
host zeroes reported pages.

Without this, a zeroed page freed via free_frozen_pages_hint could
merge with a non-zero buddy, and the merged page would falsely
appear zeroed.

Signed-off-by: Michael S. Tsirkin <[email protected]>
Assisted-by: Claude:claude-opus-4-6
Assisted-by: cursor-agent:GPT-5.4-xhigh
---
 mm/page_alloc.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index f7abbc46e725..6adc894748c8 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -984,6 +984,8 @@ static inline void __free_one_page(struct page *page,
        unsigned long buddy_pfn = 0;
        unsigned long combined_pfn;
        struct page *buddy;
+       bool buddy_zeroed;
+       bool page_zeroed;
        bool to_tail;
 
        VM_BUG_ON(!zone_is_initialized(zone));
@@ -1022,6 +1024,8 @@ static inline void __free_one_page(struct page *page,
                                goto done_merging;
                }
 
+               buddy_zeroed = PageZeroed(buddy);
+
                /*
                 * Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
                 * merge with it and move up one order.
@@ -1040,10 +1044,17 @@ static inline void __free_one_page(struct page *page,
                        change_pageblock_range(buddy, order, migratetype);
                }
 
+               page_zeroed = PageZeroed(page);
+               __ClearPageZeroed(page);
+               __ClearPageZeroed(buddy);
+
                combined_pfn = buddy_pfn & pfn;
                page = page + (combined_pfn - pfn);
                pfn = combined_pfn;
                order++;
+
+               if (page_zeroed && buddy_zeroed)
+                       __SetPageZeroed(page);
        }
 
 done_merging:
@@ -1730,7 +1741,8 @@ struct page *__pageblock_pfn_to_page(unsigned long 
start_pfn,
  * -- nyc
  */
 static inline unsigned int expand(struct zone *zone, struct page *page, int 
low,
-                                 int high, int migratetype, bool reported)
+                                 int high, int migratetype, bool reported,
+                                 bool zeroed)
 {
        unsigned int size = 1 << high;
        unsigned int nr_added = 0;
@@ -1761,6 +1773,8 @@ static inline unsigned int expand(struct zone *zone, 
struct page *page, int low,
                 */
                if (reported)
                        __SetPageReported(&page[size]);
+               if (zeroed)
+                       __SetPageZeroed(&page[size]);
        }
 
        return nr_added;
@@ -1772,9 +1786,11 @@ static __always_inline void page_del_and_expand(struct 
zone *zone,
 {
        int nr_pages = 1 << high;
        bool was_reported = page_reported(page);
+       bool was_zeroed = PageZeroed(page);
 
        __del_page_from_free_list(page, zone, high, migratetype);
-       nr_pages -= expand(zone, page, low, high, migratetype, was_reported);
+       nr_pages -= expand(zone, page, low, high, migratetype, was_reported,
+                          was_zeroed);
        account_freepages(zone, -nr_pages, migratetype);
 }
 
@@ -2333,7 +2349,7 @@ try_to_claim_block(struct zone *zone, struct page *page,
                del_page_from_free_list(page, zone, current_order, block_type);
                change_pageblock_range(page, current_order, start_type);
                nr_added = expand(zone, page, order, current_order, start_type,
-                                 false);
+                                 false, false);
                account_freepages(zone, nr_added, start_type);
                return page;
        }
-- 
MST


Reply via email to