[RFC PATCH 35/45] mm: show_mem: collect migratetype letters from per-superpageblock lists

Rik van Riel
2026-04-30T20:22:49Z

show_mem()'s per-order line includes a parenthesized set of letters
(UME, etc.) indicating which migratetypes have free pages at that
order. This was computed by checking free_area_empty() on
zone->free_area[order].free_list[type]. After the SPB rework, those
zone-level list heads are always empty -- free pages live on per-
superpageblock lists -- so the migratetype letters never appeared.

Iterate every SPB in the zone for each order, OR'ing in any non-empty
migratetype lists, with an early exit once all migratetypes have been
seen. The shadow nr_free count remains correct (zone->free_area[].
nr_free is updated by __add_to_free_list / __del_page_from_free_list
to sum across all SPBs).

Falls back to the zone-level free_area for zones whose SPB array has
not yet been allocated.

The whole loop runs under spin_lock_irqsave(&zone->lock) without
drops, so no hotplug race. Worst case work is bounded
(NR_PAGE_ORDERS * MIGRATE_TYPES * nr_superpageblocks list_empty
pointer compares per zone) and acceptable for a diagnostic path.

Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4.7 syzkaller
---
mm/show_mem.c | 25 ++++++++++++++++++++-----
1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/mm/show_mem.c b/mm/show_mem.c
index bbbbef5baed7..a80076fe6165 100644
--- a/mm/show_mem.c
+++ b/mm/show_mem.c
@@ -363,16 +363,31 @@ static void show_free_areas(unsigned int filter, nodemask_t *nodemask, int max_z

spin_lock_irqsave(&zone->lock, flags);
for (order = 0; order < NR_PAGE_ORDERS; order++) {
- struct free_area *area = &zone->free_area[order];
+ unsigned long sb_idx;
+ unsigned long nr_lists = zone->nr_superpageblocks ? : 1;
int type;

- nr[order] = area->nr_free;
+ nr[order] = zone->free_area[order].nr_free;
total += nr[order] << order;

+ /*
+ * Collect the migratetypes present at this order. After
+ * the SPB rework, free pages live on per-superpageblock
+ * free lists, so check each SPB. Stop early once all
+ * migratetypes have been observed.
+ */
types[order] = 0;
- for (type = 0; type < MIGRATE_TYPES; type++) {
- if (!free_area_empty(area, type))
- types[order] |= 1 << type;
+ for (sb_idx = 0; sb_idx < nr_lists; sb_idx++) {
+ struct free_area *area = zone->nr_superpageblocks ?
+ &zone->superpageblocks[sb_idx].free_area[order] :
+ &zone->free_area[order];
+
+ for (type = 0; type < MIGRATE_TYPES; type++) {
+ if (!free_area_empty(area, type))
+ types[order] |= 1 << type;
+ }
+ if (types[order] == (1 << MIGRATE_TYPES) - 1)
+ break;
}
}
spin_unlock_irqrestore(&zone->lock, flags);
--
2.52.0