> collapse_fork() checks that a fork-shared range collapses in the process
> that asks for it while the co-sharer keeps its own page, but the co-sharer
> sits still while that happens.

co-sharer sounds awkward, can we use parent and child?

> Add a case where the co-sharer writes to the shared range throughout the
> collapse.  CoW has to keep the two sides apart under those writes: the
> collapsing child must see the content from before the fork, and the
> writing parent must see only its own writes.
> 
> The co-sharer unshares one page every 10ms, and only once the collapsing
> side says it is about to start.  Writing the range in a burst breaks CoW
> on all of it before the collapse begins, leaving the child to collapse
> pages that are already exclusive to it.
> 
> Nothing here is new behaviour: the case passes on mainline, and locks in
> isolation that collapse already provides.

And that's imporant because?

> Assisted-by: Claude-Code:claude-opus-5
> Tested-by: Muhammad Usama Anjum <[email protected]>
> Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
>
> diff --git a/tools/testing/selftests/mm/khugepaged.c 
> b/tools/testing/selftests/mm/khugepaged.c
> index 392fd65fcc97..1d357143258a 100644
> --- a/tools/testing/selftests/mm/khugepaged.c
> +++ b/tools/testing/selftests/mm/khugepaged.c
> @@ -1218,6 +1218,97 @@ 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, child_status, i, n = shared / page_size;
> +     /* volatile: the loop below must really store, on every iteration */
> +     volatile int *ip;
> +     pid_t child;
> +     int sync[2];
> +     char go = 1;
> +     void *p;
> +
> +     p = ops->setup_area(1);
> +     ip = p;
> +     ops->fault(p, 0, shared);               /* shared prefix, pre-fork 
> pattern */
> +     if (pipe(sync))
> +             ksft_exit_fail_perror("pipe()");
> +
> +     ksft_print_msg("Fork, collapse in the child while the parent 
> rewrites...");
> +     child = fork();
> +     if (!child) {
> +             int collapse_status;
> +
> +             close(sync[0]);
> +             ops->fault(p, shared, hpage_pmd_size);  /* private remainder */
> +             /* Start the parent unsharing, and give it a head start. */
> +             if (write(sync[1], &go, 1) != 1)
> +                     _exit(KSFT_FAIL);
> +             usleep(5000);
> +             c->collapse("Collapse a range shared with a writing co-sharer",
> +                         p, 1, ops, true);
> +             collapse_status = exit_status;
> +             for (i = 0; i < n; i++)
> +                     if (ip[i * stride] != i + 0xdead0000)
> +                             break;
> +             if (i == n)
> +                     success("OK");
> +             else
> +                     fail("Fail: child content");
> +             /* The content check must not bury a failed collapse. */
> +             if (exit_status != KSFT_FAIL)
> +                     exit_status = collapse_status;
> +             ops->cleanup_area(p, hpage_pmd_size);
> +             _exit(exit_status);
> +     }
> +
> +     close(sync[1]);
> +     if (read(sync[0], &go, 1) != 1)
> +             ksft_exit_fail_msg("child never reached the collapse\n");
> +
> +     /*
> +      * Unshare one page at a time.  A burst would break CoW on all of them
> +      * in microseconds -- wait_for_scan() does not even poll for TICK --
> +      * and the child would collapse pages already exclusive to it.
> +      */
> +     i = 0;
> +     do {
> +             if (i < n)
> +                     ip[i * stride] = i + 0xbeef0000;
> +             i++;
> +             usleep(10 * 1000);
> +     } while (waitpid(child, &wstatus, WNOHANG) == 0);

Can we please not put waitpid() inside while condition?

-- 
Sincerely yours,
Mike.


Reply via email to