]> www.infradead.org Git - users/willy/pagecache.git/commitdiff
mm/contig_alloc: fix alloc_contig_range when __GFP_COMP and order < MAX_ORDER
authorJinjiang Tu <tujinjiang@huawei.com>
Wed, 12 Mar 2025 08:47:05 +0000 (16:47 +0800)
committerAndrew Morton <akpm@linux-foundation.org>
Fri, 14 Mar 2025 22:52:39 +0000 (15:52 -0700)
When calling alloc_contig_range() with __GFP_COMP and the order of
requested pfn range is pageblock_order, less than MAX_ORDER, I triggered
WARNING as follows:

 PFN range: requested [21501050882150105600), allocated [21501050882150106112)
 WARNING: CPU: 3 PID: 580 at mm/page_alloc.c:6877 alloc_contig_range+0x280/0x340

alloc_contig_range() marks pageblocks of the requested pfn range to be
isolated, migrate these pages if they are in use and will be freed to
MIGRATE_ISOLATED freelist.

Suppose two alloc_contig_range() calls at the same time and the requested
pfn range are [0x80280000, 0x80280200) and [0x80280200, 0x80280400)
respectively.  Suppose the two memory range are in use, then
alloc_contig_range() will migrate and free these pages to MIGRATE_ISOLATED
freelist.  __free_one_page() will merge MIGRATE_ISOLATE buddy to larger
buddy, resulting in a MAX_ORDER buddy.  Finally, find_large_buddy() in
alloc_contig_range() returns a MAX_ORDER buddy and results in WARNING.

To fix it, call free_contig_range() to free the excess pfn range.

Link: https://lkml.kernel.org/r/20250312084705.2938220-1-tujinjiang@huawei.com
Fixes: e98337d11bbd ("mm/contig_alloc: support __GFP_COMP")
Signed-off-by: Jinjiang Tu <tujinjiang@huawei.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Kefeng Wang <wangkefeng.wang@huawei.com>
Cc: Nanyong Sun <sunnanyong@huawei.com>
Cc: Yu Zhao <yuzhao@google.com>
Cc: Zi Yan <ziy@nvidia.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
mm/page_alloc.c

index 542d25f77be80304b731411ffd29b276ee13be0c..1ac8bea0e1cdc337f91eb268f15b27fa89465292 100644 (file)
@@ -6528,7 +6528,8 @@ int alloc_contig_range_noprof(unsigned long start, unsigned long end,
                goto done;
        }
 
-       if (!(gfp_mask & __GFP_COMP)) {
+       if (!(gfp_mask & __GFP_COMP) ||
+               (is_power_of_2(end - start) && ilog2(end - start) < MAX_PAGE_ORDER)) {
                split_free_pages(cc.freepages, gfp_mask);
 
                /* Free head and tail (if any) */
@@ -6536,7 +6537,15 @@ int alloc_contig_range_noprof(unsigned long start, unsigned long end,
                        free_contig_range(outer_start, start - outer_start);
                if (end != outer_end)
                        free_contig_range(end, outer_end - end);
-       } else if (start == outer_start && end == outer_end && is_power_of_2(end - start)) {
+
+               outer_start = start;
+               outer_end = end;
+
+               if (!(gfp_mask & __GFP_COMP))
+                       goto done;
+       }
+
+       if (start == outer_start && end == outer_end && is_power_of_2(end - start)) {
                struct page *head = pfn_to_page(start);
                int order = ilog2(end - start);