Assert that we correctly merge VMAs containing VM_SOFTDIRTY flags now that
we correctly handle these as sticky.
In order to do so, we have to account for the fact the pagemap interface
checks soft dirty PTEs and additionally that newly merged VMAs are marked
VM_SOFTDIRTY.
To account for this we use unfaulted anon VMAs, mapping one VMA in and
clearing soft-dirty, then another separate from the first which will be
marked soft-dirty which we then mremap() into place.
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
---
tools/testing/selftests/mm/soft-dirty.c | 51 ++++++++++++++++++++++++-
1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c
index 4ee4db3750c1..bb29edb1e2a3 100644
--- a/tools/testing/selftests/mm/soft-dirty.c
+++ b/tools/testing/selftests/mm/soft-dirty.c
@@ -184,6 +184,54 @@ static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
close(test_fd);
}
+static void test_merge(int pagemap_fd, int pagesize)
+{
+ char *reserved, *map, *map2;
+
+ /* Reserve space. */
+ reserved = mmap(NULL, 4 * pagesize, PROT_NONE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ if (reserved == MAP_FAILED)
+ ksft_exit_fail_msg("mmap failed\n");
+ munmap(reserved, 4 * pagesize);
+
+ /* Map a page. */
+ map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
+ if (map == MAP_FAILED)
+ ksft_exit_fail_msg("mmap failed\n");
+
+ /* This will clear VM_SOFTDIRTY too. */
+ clear_softdirty();
+
+ /*
+ * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from
+ * map.
+ */
+ map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
+ if (map2 == MAP_FAILED)
+ ksft_exit_fail_msg("mmap failed\n");
+
+ /*
+ * Now remap it immediately adjacent to map, if the merge correctly
+ * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole
+ * being marked soft-dirty.
+ */
+ map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE,
+ &reserved[2 * pagesize]);
+ if (map2 == MAP_FAILED)
+ ksft_exit_fail_msg("mremap failed\n");
+ ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
+ "Test %s-anon soft-dirty after merge 1st pg\n",
+ __func__);
+ ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
+ "Test %s-anon soft-dirty after merge 2nd pg\n",
+ __func__);
+
+ munmap(map, 2 * pagesize);
+}
+
static void test_mprotect_anon(int pagemap_fd, int pagesize)
{
test_mprotect(pagemap_fd, pagesize, true);
@@ -204,7 +252,7 @@ int main(int argc, char **argv)
if (!softdirty_supported())
ksft_exit_skip("soft-dirty is not support\n");
- ksft_set_plan(15);
+ ksft_set_plan(17);
pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
if (pagemap_fd < 0)
ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
@@ -216,6 +264,7 @@ int main(int argc, char **argv)
test_hugepage(pagemap_fd, pagesize);
test_mprotect_anon(pagemap_fd, pagesize);
test_mprotect_file(pagemap_fd, pagesize);
+ test_merge(pagemap_fd, pagesize);
close(pagemap_fd);
--
2.51.0
On 14.11.25 18:53, Lorenzo Stoakes wrote:
> Assert that we correctly merge VMAs containing VM_SOFTDIRTY flags now that
> we correctly handle these as sticky.
>
> In order to do so, we have to account for the fact the pagemap interface
> checks soft dirty PTEs and additionally that newly merged VMAs are marked
> VM_SOFTDIRTY.
>
> To account for this we use unfaulted anon VMAs, mapping one VMA in and
> clearing soft-dirty, then another separate from the first which will be
> marked soft-dirty which we then mremap() into place.
>
> Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
> ---
> tools/testing/selftests/mm/soft-dirty.c | 51 ++++++++++++++++++++++++-
> 1 file changed, 50 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c
> index 4ee4db3750c1..bb29edb1e2a3 100644
> --- a/tools/testing/selftests/mm/soft-dirty.c
> +++ b/tools/testing/selftests/mm/soft-dirty.c
> @@ -184,6 +184,54 @@ static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
> close(test_fd);
> }
>
> +static void test_merge(int pagemap_fd, int pagesize)
> +{
> + char *reserved, *map, *map2;
> +
> + /* Reserve space. */
It took me a while to figure out why you are using 4 pages. I guess you
want to make sure that we don't end up merging to the left (or the
right). A diagram would have helped me.
> + reserved = mmap(NULL, 4 * pagesize, PROT_NONE,
> + MAP_ANON | MAP_PRIVATE, -1, 0);
> + if (reserved == MAP_FAILED)
> + ksft_exit_fail_msg("mmap failed\n");
> + munmap(reserved, 4 * pagesize);
> +
> + /* Map a page. */
Note that we are not actually "mapping a page". "Create a new page-sized
VMA" or sth like that.
> + map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
> + MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
> + if (map == MAP_FAILED)
> + ksft_exit_fail_msg("mmap failed\n");
> +
> + /* This will clear VM_SOFTDIRTY too. */
> + clear_softdirty();
> +
> + /*
> + * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from
> + * map.
Could we have something "to the right" of this new VMA that we might be
merging with (that might interfere?) and if so, do we care?
Just wondering if we would actually want a reserved area that spans 5
page sizes to rule out these cases.
mmap1
[ empty ][ VMA1 ][ empty ]
mmap2
[ empty ][ VMA1 ][ empty ][ VMA2 ][ empty ]
mremap
[ empty ][ VMA1 ][ VMA2 ][ empty ]
which is after the merge
[ empty ][ VMA (SD) ][ ]
--
Cheers
David
On Mon, Nov 17, 2025 at 03:44:46PM +0100, David Hildenbrand (Red Hat) wrote:
> On 14.11.25 18:53, Lorenzo Stoakes wrote:
> > Assert that we correctly merge VMAs containing VM_SOFTDIRTY flags now that
> > we correctly handle these as sticky.
> >
> > In order to do so, we have to account for the fact the pagemap interface
> > checks soft dirty PTEs and additionally that newly merged VMAs are marked
> > VM_SOFTDIRTY.
> >
> > To account for this we use unfaulted anon VMAs, mapping one VMA in and
> > clearing soft-dirty, then another separate from the first which will be
> > marked soft-dirty which we then mremap() into place.
> >
> > Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
> > ---
> > tools/testing/selftests/mm/soft-dirty.c | 51 ++++++++++++++++++++++++-
> > 1 file changed, 50 insertions(+), 1 deletion(-)
> >
> > diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c
> > index 4ee4db3750c1..bb29edb1e2a3 100644
> > --- a/tools/testing/selftests/mm/soft-dirty.c
> > +++ b/tools/testing/selftests/mm/soft-dirty.c
> > @@ -184,6 +184,54 @@ static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
> > close(test_fd);
> > }
> > +static void test_merge(int pagemap_fd, int pagesize)
> > +{
> > + char *reserved, *map, *map2;
> > +
> > + /* Reserve space. */
>
> It took me a while to figure out why you are using 4 pages. I guess you want
> to make sure that we don't end up merging to the left (or the right). A
> diagram would have helped me.
I have spoiled everybody too much with my ASCII diagrams ;)
But _just for you_ I will make one again ;))
>
> > + reserved = mmap(NULL, 4 * pagesize, PROT_NONE,
> > + MAP_ANON | MAP_PRIVATE, -1, 0);
> > + if (reserved == MAP_FAILED)
> > + ksft_exit_fail_msg("mmap failed\n");
> > + munmap(reserved, 4 * pagesize);
> > +
> > + /* Map a page. */
>
> Note that we are not actually "mapping a page". "Create a new page-sized
> VMA" or sth like that.
Lol depends on your definition I suppose. Not in the sense of page table
mappings. I guess this is fairly redundant anyway so can dorp
>
> > + map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
> > + MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
> > + if (map == MAP_FAILED)
> > + ksft_exit_fail_msg("mmap failed\n");
> > +
> > + /* This will clear VM_SOFTDIRTY too. */
> > + clear_softdirty();
> > +
> > + /*
> > + * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from
> > + * map.
>
> Could we have something "to the right" of this new VMA that we might be
> merging with (that might interfere?) and if so, do we care?
>
> Just wondering if we would actually want a reserved area that spans 5 page
> sizes to rule out these cases.
>
You're right! This is an oversight, will fix.
> mmap1
>
> [ empty ][ VMA1 ][ empty ]
>
> mmap2
>
> [ empty ][ VMA1 ][ empty ][ VMA2 ][ empty ]
>
> mremap
>
> [ empty ][ VMA1 ][ VMA2 ][ empty ]
>
> which is after the merge
>
> [ empty ][ VMA (SD) ][ ]
Yeah this is the purpose of adding space around.
I also want to add an additional test anyway so can do both things at once
:)
>
>
> --
> Cheers
>
> David
Cheers, Lorenzo
© 2016 - 2026 Red Hat, Inc.