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

collapse_fork already checks that a fork-shared range collapses in the
process that asks while a co-sharer keeps its own page, but with the
co-sharer quiescent. Add a case that keeps a co-sharer writing to the
shared range throughout the collapse: CoW must keep the two sides'
content independent -- the collapsing child sees the pre-fork content,
the writing parent sees only its own writes. This exercises the
requirement that CoW keeps a shared source isolated from the collapsing
side throughout the collapse, even under an actively writing co-sharer.

Passes on the unmodified kernel, pinning down the CoW-isolation contract
that khugepaged collapse already provides.

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

diff --git a/tools/testing/selftests/mm/khugepaged.c 
b/tools/testing/selftests/mm/khugepaged.c
index 65fafab06410..81001e15765c 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -1047,6 +1047,61 @@ static void collapse_max_ptes_shared(struct 
collapse_context *c, struct mem_ops
        ksft_test_result_report(exit_status, "%s\n", __func__);
 }
 
+/*
+ * Content stays isolated while a co-sharer writes concurrently. A shared
+ * source is copied live (not frozen), relying on it being CoW - immutable
+ * for the duration of the copy; a co-sharer's write goes to a CoW copy. The
+ * collapsing child must see the pre-fork content, the writing parent only
+ * its own writes.
+ */
+static void collapse_fork_cow_race(struct collapse_context *c, struct mem_ops 
*ops)
+{
+       const unsigned long shared = 64 * page_size;
+       const int stride = page_size / sizeof(int);
+       int wstatus, i, n = shared / page_size;
+       int *ip;
+       void *p;
+
+       p = ops->setup_area(1);
+       ip = p;
+       ops->fault(p, 0, shared);               /* shared prefix, pre-fork 
pattern */
+
+       ksft_print_msg("Fork, collapse in the child while the parent 
rewrites...");
+       if (!fork()) {
+               ops->fault(p, shared, hpage_pmd_size);  /* private remainder */
+               c->collapse("Collapse a range shared with a writing co-sharer",
+                           p, 1, ops, true);
+               for (i = 0; i < n; i++)
+                       if (ip[i * stride] != i + 0xdead0000) {
+                               fail("Fail: child content");
+                               ops->cleanup_area(p, hpage_pmd_size);
+                               _exit(exit_status);
+                       }
+               success("OK");
+               ops->cleanup_area(p, hpage_pmd_size);
+               _exit(exit_status);
+       }
+
+       /* Hammer the parent's own writes over the shared prefix. */
+       for (int it = 0; it < 200000; it++)
+               for (i = 0; i < n; i++)
+                       ip[i * stride] = i + 0xbeef0000;
+
+       wait(&wstatus);
+       exit_status = WEXITSTATUS(wstatus);
+
+       ksft_print_msg("Check the parent sees only its own writes...");
+       for (i = 0; i < n; i++)
+               if (ip[i * stride] != i + 0xbeef0000)
+                       break;
+       if (i == n)
+               success("OK");
+       else
+               fail("Fail: parent content");
+       ops->cleanup_area(p, hpage_pmd_size);
+       ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
 static void madvise_collapse_existing_thps(struct collapse_context *c,
                                           struct mem_ops *ops)
 {
@@ -1595,6 +1650,9 @@ int main(int argc, char **argv)
        TEST(collapse_max_ptes_shared, khugepaged_context, anon_ops);
        TEST(collapse_max_ptes_shared, madvise_context, anon_ops);
 
+       TEST(collapse_fork_cow_race, khugepaged_context, anon_ops);
+       TEST(collapse_fork_cow_race, madvise_context, anon_ops);
+
        TEST(madvise_collapse_existing_thps, madvise_context, anon_ops);
        TEST(madvise_collapse_existing_thps, madvise_context, 
read_only_file_ops);
        TEST(madvise_collapse_existing_thps, madvise_context, 
read_write_file_read_ops);
-- 
2.54.0


Reply via email to