From: "Kiryl Shutsemau (Meta)" <[email protected]>

A GUP pin holds a reference nobody else can account for, so a pinned page
cannot be collapsed.  The freeze needs the folio's reference count to
match what its mappings and cache membership explain, and a pin makes it
not.

The window holding one has to be refused.  The windows beside it should
not be, as with a lazyfree page.

Pin one page for the duration of a khugepaged pass through gup_test, which
makes the refusal deterministic instead of a race.  Check the same three
things the lazyfree case does: the rest of the table collapsed, that
window not, and the pinned page still backed by an order-0 folio, since
selection descends orders on a refusal.

Skipped without CONFIG_GUP_TEST or the privilege to use it.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
---
 tools/testing/selftests/mm/khugepaged.c | 64 +++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/tools/testing/selftests/mm/khugepaged.c 
b/tools/testing/selftests/mm/khugepaged.c
index 58cb7364652e..bd684cd25fed 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -21,7 +21,9 @@
 
 #include "linux/magic.h"
 
+#include <sys/ioctl.h>
 #include "vm_util.h"
+#include "../../../../mm/gup_test.h"
 #include "hugepage_settings.h"
 
 #define BASE_ADDR ((void *)(1UL << 30))
@@ -1728,6 +1730,67 @@ static void collapse_order_lazyfree_window(struct 
collapse_context *c,
        ksft_test_result_report(exit_status, "%s\n", __func__);
 }
 
+/*
+ * A GUP pin on one page holds a reference the collapse cannot account for, so
+ * khugepaged must leave the window holding it alone -- and collapse the rest.
+ * The pin is held across the whole khugepaged pass, which is what makes the
+ * refusal deterministic rather than a race.
+ */
+static void collapse_order_pinned_window(struct collapse_context *c,
+                                        struct mem_ops *ops)
+{
+       struct pin_longterm_test pin = {};
+       size_t window = mthp_window_size();
+       void *p;
+       int fd;
+
+       fd = open("/sys/kernel/debug/gup_test", O_RDWR);
+       if (fd < 0) {
+               ksft_test_result_skip("%s: gup_test needs CONFIG_GUP_TEST and 
root\n",
+                                     __func__);
+               return;
+       }
+
+       mthp_push_target_order();
+
+       p = ops->setup_area(1);
+       ops->fault(p, 0, hpage_pmd_size);
+       if (!window_not_collapsed(p, hpage_pmd_size))
+               ksft_exit_fail_msg("Unexpected large folio after fault\n");
+
+       pin.addr = (__u64)(unsigned long)p;
+       pin.size = page_size;
+       pin.flags = PIN_LONGTERM_TEST_FLAG_USE_WRITE;
+       if (ioctl(fd, PIN_LONGTERM_TEST_START, &pin)) {
+               ops->cleanup_area(p, hpage_pmd_size);
+               close(fd);
+               thp_pop_settings();
+               ksft_test_result_skip("%s: cannot pin\n", __func__);
+               return;
+       }
+
+       madvise(p, hpage_pmd_size, MADV_HUGEPAGE);
+       ksft_print_msg("Collapse the windows beside a pinned page...");
+       if (!khugepaged_wait_full_pass())
+               fail("Timeout");
+       else if (window_collapsed(p + window, hpage_pmd_size - window) &&
+                window_not_collapsed(p, window) &&
+                /* Left alone at every order, not just the target one */
+                is_range_backed_by_folio_orders(p, page_size, 0,
+                                                pagemap_fd, kpageflags_fd))
+               success("OK");
+       else
+               fail("Fail");
+
+       ioctl(fd, PIN_LONGTERM_TEST_STOP);
+       close(fd);
+
+       validate_memory(p, 0, hpage_pmd_size);
+       ops->cleanup_area(p, hpage_pmd_size);
+       thp_pop_settings();
+       ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
 static void usage(void)
 {
        fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] <test type> 
[dir]\n\n");
@@ -2020,6 +2083,7 @@ int main(int argc, char **argv)
                TEST(collapse_order_sub_pmd_holes, mthp_khugepaged_context, 
anon_ops);
                TEST(collapse_order_mlocked, mthp_khugepaged_context, anon_ops);
                TEST(collapse_order_lazyfree_window, mthp_khugepaged_context, 
anon_ops);
+               TEST(collapse_order_pinned_window, mthp_khugepaged_context, 
anon_ops);
        }
 
        TEST(collapse_full, madvise_context, anon_ops);
-- 
2.54.0


Reply via email to