]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
mm/alloc_tag: replace fixed-size early PFN array with dynamic linked list
authorHao Ge <hao.ge@linux.dev>
Thu, 4 Jun 2026 02:40:08 +0000 (10:40 +0800)
committerAndrew Morton <akpm@linux-foundation.org>
Sun, 21 Jun 2026 18:31:28 +0000 (11:31 -0700)
Pages allocated before page_ext is available have their codetag left
uninitialized.  Track these early PFNs and clear their codetag in
clear_early_alloc_pfn_tag_refs() to avoid "alloc_tag was not set" warnings
when they are freed later.

Currently a fixed-size array of 8192 entries is used, with a warning if
the limit is exceeded.  However, the number of early allocations depends
on the number of CPUs and can be larger than 8192.

Replace the fixed-size array with a dynamically allocated linked list of
pfn_pool structs.  Each node is allocated via alloc_page() and mapped to a
pfn_pool containing a next pointer, an atomic slot counter, and a PFN
array that fills the remainder of the page.

The tracking pages themselves are allocated via alloc_page(), which would
trigger __pgalloc_tag_add() -> alloc_tag_add_early_pfn() and recurse
indefinitely.  Introduce __GFP_NO_CODETAG (reuses the %__GFP_NO_OBJ_EXT
bit) and pass gfp_flags through pgalloc_tag_add() so that the early path
can skip recording allocations that carry this flag.

Link: https://lore.kernel.org/20260604024008.46592-1-hao.ge@linux.dev
Signed-off-by: Hao Ge <hao.ge@linux.dev>
Suggested-by: Suren Baghdasaryan <surenb@google.com>
Acked-by: Suren Baghdasaryan <surenb@google.com>
Cc: Kent Overstreet <kent.overstreet@linux.dev>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/alloc_tag.h
lib/alloc_tag.c
mm/page_alloc.c

index 02de2ede560f3a6c0a948cebdd5c4fd6263cea5a..068ba2e77c5d65a362826c5f5a848bb470f1bbbf 100644 (file)
@@ -163,11 +163,11 @@ static inline void alloc_tag_sub_check(union codetag_ref *ref)
 {
        WARN_ONCE(ref && !ref->ct, "alloc_tag was not set\n");
 }
-void alloc_tag_add_early_pfn(unsigned long pfn);
+void alloc_tag_add_early_pfn(unsigned long pfn, gfp_t gfp_flags);
 #else
 static inline void alloc_tag_add_check(union codetag_ref *ref, struct alloc_tag *tag) {}
 static inline void alloc_tag_sub_check(union codetag_ref *ref) {}
-static inline void alloc_tag_add_early_pfn(unsigned long pfn) {}
+static inline void alloc_tag_add_early_pfn(unsigned long pfn, gfp_t gfp_flags) {}
 #endif
 
 /* Caller should verify both ref and tag to be valid */
index ed1bdcf1f8abfcc3f59a7cb482c9b14c79894956..f2f574bcf3834313fe689709410b92c10cc3965b 100644 (file)
@@ -767,50 +767,82 @@ static __init bool need_page_alloc_tagging(void)
  * their codetag uninitialized. Track these early PFNs so we can clear
  * their codetag refs later to avoid warnings when they are freed.
  *
- * Early allocations include:
- *   - Base allocations independent of CPU count
- *   - Per-CPU allocations (e.g., CPU hotplug callbacks during smp_init,
- *     such as trace ring buffers, scheduler per-cpu data)
- *
- * For simplicity, we fix the size to 8192.
- * If insufficient, a warning will be triggered to alert the user.
+ * Each page is cast to a pfn_pool: the first few bytes hold metadata
+ * (next pointer and slot count), the remainder stores PFNs.
+ */
+struct pfn_pool {
+       struct pfn_pool *next;
+       atomic_t count;
+       unsigned long pfns[];
+};
+
+#define PFN_POOL_SIZE                  ((PAGE_SIZE - offsetof(struct pfn_pool, pfns)) / \
+                                        sizeof(unsigned long))
+
+/*
+ * Skip early PFN recording for a page allocation.  Reuses the
+ * %__GFP_NO_OBJ_EXT bit.  Used by __alloc_tag_add_early_pfn() to avoid
+ * recursion when allocating pages for the early PFN tracking list
+ * itself.
  *
- * TODO: Replace fixed-size array with dynamic allocation using
- * a GFP flag similar to ___GFP_NO_OBJ_EXT to avoid recursion.
+ * Codetags of the pages allocated with __GFP_NO_CODETAG should be
+ * cleared (via clear_page_tag_ref()) before freeing the pages to prevent
+ * alloc_tag_sub_check() from triggering a warning.
  */
-#define EARLY_ALLOC_PFN_MAX            8192
+#define __GFP_NO_CODETAG               __GFP_NO_OBJ_EXT
 
-static unsigned long early_pfns[EARLY_ALLOC_PFN_MAX] __initdata;
-static atomic_t early_pfn_count __initdata = ATOMIC_INIT(0);
+static struct pfn_pool *current_pfn_pool __initdata;
 
 static void __init __alloc_tag_add_early_pfn(unsigned long pfn)
 {
-       int old_idx, new_idx;
+       struct pfn_pool *pool;
+       int idx;
 
        do {
-               old_idx = atomic_read(&early_pfn_count);
-               if (old_idx >= EARLY_ALLOC_PFN_MAX) {
-                       pr_warn_once("Early page allocations before page_ext init exceeded EARLY_ALLOC_PFN_MAX (%d)\n",
-                                     EARLY_ALLOC_PFN_MAX);
-                       return;
+               pool = READ_ONCE(current_pfn_pool);
+               if (!pool || atomic_read(&pool->count) >= PFN_POOL_SIZE) {
+                       struct page *new_page = alloc_page(__GFP_HIGH | __GFP_NO_CODETAG);
+                       struct pfn_pool *new;
+
+                       if (!new_page) {
+                               pr_warn_once("early PFN tracking page allocation failed\n");
+                               return;
+                       }
+                       new = page_address(new_page);
+                       new->next = pool;
+                       atomic_set(&new->count, 0);
+                       if (cmpxchg(&current_pfn_pool, pool, new) != pool) {
+                               clear_page_tag_ref(new_page);
+                               __free_page(new_page);
+                               continue;
+                       }
+                       pool = new;
                }
-               new_idx = old_idx + 1;
-       } while (!atomic_try_cmpxchg(&early_pfn_count, &old_idx, new_idx));
+               idx = atomic_read(&pool->count);
+               if (idx >= PFN_POOL_SIZE)
+                       continue;
+               if (atomic_cmpxchg(&pool->count, idx, idx + 1) == idx)
+                       break;
+       } while (1);
 
-       early_pfns[old_idx] = pfn;
+       pool->pfns[idx] = pfn;
 }
 
 typedef void alloc_tag_add_func(unsigned long pfn);
 static alloc_tag_add_func __rcu *alloc_tag_add_early_pfn_ptr __refdata =
        RCU_INITIALIZER(__alloc_tag_add_early_pfn);
 
-void alloc_tag_add_early_pfn(unsigned long pfn)
+void alloc_tag_add_early_pfn(unsigned long pfn, gfp_t gfp_flags)
 {
        alloc_tag_add_func *alloc_tag_add;
 
        if (static_key_enabled(&mem_profiling_compressed))
                return;
 
+       /* Skip allocations for the tracking list itself to avoid recursion. */
+       if (gfp_flags & __GFP_NO_CODETAG)
+               return;
+
        rcu_read_lock();
        alloc_tag_add = rcu_dereference(alloc_tag_add_early_pfn_ptr);
        if (alloc_tag_add)
@@ -820,7 +852,9 @@ void alloc_tag_add_early_pfn(unsigned long pfn)
 
 static void __init clear_early_alloc_pfn_tag_refs(void)
 {
-       unsigned int i;
+       struct pfn_pool *pool, *next;
+       struct page *page;
+       int i;
 
        if (static_key_enabled(&mem_profiling_compressed))
                return;
@@ -829,37 +863,45 @@ static void __init clear_early_alloc_pfn_tag_refs(void)
        /* Make sure we are not racing with __alloc_tag_add_early_pfn() */
        synchronize_rcu();
 
-       for (i = 0; i < atomic_read(&early_pfn_count); i++) {
-               unsigned long pfn = early_pfns[i];
-
-               if (pfn_valid(pfn)) {
-                       struct page *page = pfn_to_page(pfn);
-                       union pgtag_ref_handle handle;
-                       union codetag_ref ref;
-
-                       if (get_page_tag_ref(page, &ref, &handle)) {
-                               /*
-                                * An early-allocated page could be freed and reallocated
-                                * after its page_ext is initialized but before we clear it.
-                                * In that case, it already has a valid tag set.
-                                * We should not overwrite that valid tag with CODETAG_EMPTY.
-                                *
-                                * Note: there is still a small race window between checking
-                                * ref.ct and calling set_codetag_empty(). We accept this
-                                * race as it's unlikely and the extra complexity of atomic
-                                * cmpxchg is not worth it for this debug-only code path.
-                                */
-                               if (ref.ct) {
+       for (pool = current_pfn_pool; pool; pool = next) {
+               int nr_pfns = atomic_read(&pool->count);
+
+               for (i = 0; i < nr_pfns; i++) {
+                       unsigned long pfn = pool->pfns[i];
+
+                       if (pfn_valid(pfn)) {
+                               union pgtag_ref_handle handle;
+                               union codetag_ref ref;
+
+                               if (get_page_tag_ref(pfn_to_page(pfn), &ref, &handle)) {
+                                       /*
+                                        * An early-allocated page could be freed and reallocated
+                                        * after its page_ext is initialized but before we clear it.
+                                        * In that case, it already has a valid tag set.
+                                        * We should not overwrite that valid tag
+                                        * with CODETAG_EMPTY.
+                                        *
+                                        * Note: there is still a small race window between checking
+                                        * ref.ct and calling set_codetag_empty(). We accept this
+                                        * race as it's unlikely and the extra complexity of atomic
+                                        * cmpxchg is not worth it for this debug-only code path.
+                                        */
+                                       if (ref.ct) {
+                                               put_page_tag_ref(handle);
+                                               continue;
+                                       }
+
+                                       set_codetag_empty(&ref);
+                                       update_page_tag_ref(handle, &ref);
                                        put_page_tag_ref(handle);
-                                       continue;
                                }
-
-                               set_codetag_empty(&ref);
-                               update_page_tag_ref(handle, &ref);
-                               put_page_tag_ref(handle);
                        }
                }
 
+               next = pool->next;
+               page = virt_to_page(pool);
+               clear_page_tag_ref(page);
+               __free_page(page);
        }
 }
 #else /* !CONFIG_MEM_ALLOC_PROFILING_DEBUG */
index f7db8f049bd235b62349054abd37e84952f6872e..81a9d4d1e6c0a70654907dda7327a175b929720c 100644 (file)
@@ -1246,7 +1246,7 @@ void __clear_page_tag_ref(struct page *page)
 /* Should be called only if mem_alloc_profiling_enabled() */
 static noinline
 void __pgalloc_tag_add(struct page *page, struct task_struct *task,
-                      unsigned int nr)
+                      unsigned int nr, gfp_t gfp_flags)
 {
        union pgtag_ref_handle handle;
        union codetag_ref ref;
@@ -1260,17 +1260,17 @@ void __pgalloc_tag_add(struct page *page, struct task_struct *task,
                 * page_ext is not available yet, record the pfn so we can
                 * clear the tag ref later when page_ext is initialized.
                 */
-               alloc_tag_add_early_pfn(page_to_pfn(page));
+               alloc_tag_add_early_pfn(page_to_pfn(page), gfp_flags);
                if (task->alloc_tag)
                        alloc_tag_set_inaccurate(task->alloc_tag);
        }
 }
 
 static inline void pgalloc_tag_add(struct page *page, struct task_struct *task,
-                                  unsigned int nr)
+                                  unsigned int nr, gfp_t gfp_flags)
 {
        if (mem_alloc_profiling_enabled())
-               __pgalloc_tag_add(page, task, nr);
+               __pgalloc_tag_add(page, task, nr, gfp_flags);
 }
 
 /* Should be called only if mem_alloc_profiling_enabled() */
@@ -1303,7 +1303,7 @@ static inline void pgalloc_tag_sub_pages(struct alloc_tag *tag, unsigned int nr)
 #else /* CONFIG_MEM_ALLOC_PROFILING */
 
 static inline void pgalloc_tag_add(struct page *page, struct task_struct *task,
-                                  unsigned int nr) {}
+                                  unsigned int nr, gfp_t gfp_flags) {}
 static inline void pgalloc_tag_sub(struct page *page, unsigned int nr) {}
 static inline void pgalloc_tag_sub_pages(struct alloc_tag *tag, unsigned int nr) {}
 
@@ -1858,7 +1858,7 @@ inline void post_alloc_hook(struct page *page, unsigned int order,
 
        set_page_owner(page, order, gfp_flags);
        page_table_check_alloc(page, order);
-       pgalloc_tag_add(page, current, 1 << order);
+       pgalloc_tag_add(page, current, 1 << order, gfp_flags);
 }
 
 static void prep_new_page(struct page *page, unsigned int order, gfp_t gfp_flags,