From: "Kiryl Shutsemau (Meta)" <[email protected]> pages_to_scan is meant to bound what one khugepaged pass does, but collapse_scan_mm_slot() tested it in only one place: after a table had been scanned and turned out to hold nothing.
Neither of the other two ways of spending the budget reached that test. A VMA the pass skips is charged for and walked past without asking -- one no order can be collapsed at, or one the cursor is already past the end of. A table that does hold a candidate leaves through the collapse. So a pass over an address space of thousands of VMAs khugepaged cannot use walks every one of them, however low pages_to_scan is set. Ask at the top of both loops instead, where the other reasons to stop a pass are already asked. The outer loop asks before it judges a VMA, the inner one before it scans a table. Stopping the outer loop only works if the cursor moves, and it did not for a skipped VMA. Advance khugepaged_scan.address past one, so a pass that runs out of budget resumes after the VMAs it has already judged. Without that, an address space with more skippable VMAs than the budget would be walked from the same place every pass and never scanned at all. pages_to_scan now bounds a pass that finds nothing to collapse, where before it did not. Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]> --- mm/khugepaged.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index f3a7aad5e8f2..cc5ff429d811 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -553,9 +553,19 @@ static void collapse_scan_mm_slot(unsigned int progress_max, cc->progress++; break; } + + /* + * Before the VMA is judged, so that a pass over an address space + * of VMAs it skips is bounded by the budget too: each one is + * charged for, and none of them was being asked to be scanned. + */ + if (cc->progress >= progress_max) + break; + orders = collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED); if (!orders) { + khugepaged_scan.address = vma->vm_end; cc->progress++; continue; } @@ -570,6 +580,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max, hstart = ALIGN(vma->vm_start, window); hend = ALIGN_DOWN(vma->vm_end, window); if (khugepaged_scan.address > hend) { + khugepaged_scan.address = vma->vm_end; cc->progress++; continue; } @@ -584,7 +595,8 @@ static void collapse_scan_mm_slot(unsigned int progress_max, range_end = min(hend, pmd_addr + HPAGE_PMD_SIZE); cond_resched(); - if (unlikely(collapse_test_exit_or_disable(mm))) + if (unlikely(collapse_test_exit_or_disable(mm)) || + cc->progress >= progress_max) goto breakouterloop; VM_WARN_ON_ONCE(khugepaged_scan.address < hstart); @@ -596,8 +608,6 @@ static void collapse_scan_mm_slot(unsigned int progress_max, /* If nothing to collapse, the lock is still ours */ if (!collapse_scan_pmd(vma, start, range_end, cc, orders)) { *result = cc->scan_refusal; - if (cc->progress >= progress_max) - goto breakouterloop; continue; } -- 2.54.0
