From 213753c71e098a5bca7866a147bfd262cf9d347c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 21 Sep 2026 05:30:27 -0700 Subject: [PATCH] mm: kmemleak: scan the struct page array in MAX_SCAN_SIZE batches scan_zone_pages() scans the struct page array one page per scan_block() call: if (scan_block(page, page + 1, NULL)) scan_block() takes kmemleak_lock with interrupts disabled for the duration of the call, so this acquires the lock once per online PFN to scan a single struct page. Gather runs of adjacent eligible struct pages and pass each run to scan_block() in one call, capped at MAX_SCAN_SIZE. The longest kmemleak_lock hold duration is now MAX_SCAN_SIZE worth of bytes. The same bound scan_large_block() already applies to the data sections and the per-CPU areas. This can make the scan faster. On an arm64 VM with 24 GiB and ~17 GiB in use, the struct page phase goes from 4390k scan_block() calls down to 69k for the same 4390k pages scanned, and the whole scan about 20% faster (on debug kernel). The win is in the per-acquisition cost, so on a kernel built without the lock debugging options the scan time is unchanged. I don't think it will be a problem doing more on scan_block(), given we have the scan_should_stop() protection. Coverage is unchanged: a temporary assertion comparing the number of eligible pages against the number actually passed to scan_block() matched on every zone of every scan, including while memory was being freed underneath the scan. Link: https://lore.kernel.org/20260921-b4-kmemleak-page-scan-v1-2-fb97d4801b3a@debian.org Signed-off-by: Breno Leitao Signed-off-by: Andrew Morton Reviewed-by: Catalin Marinas --- mm/kmemleak.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index fe34c5650f42..20c0eb41c873 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -1862,8 +1862,11 @@ static void dedup_flush(struct xarray *dedup) */ static int scan_zone_pages(struct zone *zone) { + const unsigned int max_batch = MAX_SCAN_SIZE / sizeof(struct page); unsigned long start_pfn = zone->zone_start_pfn; unsigned long end_pfn = zone_end_pfn(zone); + struct page *first = NULL, *last = NULL; + unsigned int batch = 0; unsigned long pfn; for (pfn = start_pfn; pfn < end_pfn; pfn++) { @@ -1872,19 +1875,29 @@ static int scan_zone_pages(struct zone *zone) if (!(pfn & 63)) cond_resched_tasks_rcu_qs(); - if (!page) - continue; + /* only scan in-use pages belonging to this zone */ + if (page && (page_zone(page) != zone || + page_count(page) == 0)) + page = NULL; - /* only scan pages belonging to this zone */ - if (page_zone(page) != zone) + if (page && first && page == last + 1 && + batch < max_batch) { + last = page; + batch++; continue; - /* only scan if page is in use */ - if (page_count(page) == 0) - continue; - if (scan_block(page, page + 1, NULL)) + } + + if (first && scan_block(first, last + 1, NULL)) return 1; + + first = page; + last = page; + batch = page ? 1 : 0; } + if (first && scan_block(first, last + 1, NULL)) + return 1; + return 0; }