selftests/mm: Add UFFDIO_MOVE huge zeropage PMD regression test
Subsystems (5)
PATCH v2 3/3 selftests/mm: Add UFFDIO_MOVE huge zeropage PMD regression test
The existing uffd-unit-tests move-pmd coverage exercises PMD-sized UFFDIO_MOVE on anonymous THPs, but it does not force the huge zeropage PMD path in move_pages_huge_pmd().
Add a dedicated anonymous UFFDIO_MOVE PMD test that exercises this relatively comprehensively.
--- tools/testing/selftests/mm/uffd-unit-tests.c | 176 +++++++++++++++++++ 1 file changed, 176 insertions(+)
tools/testing/selftests/mm/uffd-unit-tests.c +176 -0
@@ -203,6 +203,62 @@ static int pagemap_open(void)return fd; } +static long uffd_pagemap_scan_get_categories_raw (int fd,char *start, +struct page_region *r) +{ +struct pm_scan_arg arg = {0 }; + + arg.start = (uintptr_t )start; + arg.end = (uintptr_t )(start +psize ()); + arg.vec = (uintptr_t )r; + arg.vec_len =1 ; + arg.flags =0 ; + arg.size =sizeof (struct pm_scan_arg ); + arg.max_pages =0 ; + arg.category_inverted =0 ; + arg.category_mask =0 ; + arg.category_anyof_mask =PAGE_IS_WPALLOWED |PAGE_IS_WRITTEN | +PAGE_IS_FILE |PAGE_IS_PRESENT | +PAGE_IS_SWAPPED |PAGE_IS_PFNZERO | +PAGE_IS_HUGE |PAGE_IS_SOFT_DIRTY ; + arg.return_mask = arg.category_anyof_mask; + +return ioctl (fd,PAGEMAP_SCAN , &arg); +} + +static bool uffd_pagemap_scan_supported (int fd,char *start) +{ +static int supported =-1 ; +int ret; + +if (supported !=-1 ) +return supported; + + ret =uffd_pagemap_scan_get_categories_raw (fd, start, + (struct page_region *)~0UL ); +if (ret ==0 ) +err ("PAGEMAP_SCAN succeeded unexpectedly" ); + + supported = errno ==EFAULT ; +return supported; +} + +static bool uffd_pagemap_scan_get_categories (int fd,char *start,uint64_t *categories) +{ +struct page_region r = {0 }; +long ret; + +if (!uffd_pagemap_scan_supported (fd, start)) +return false; + + ret =uffd_pagemap_scan_get_categories_raw (fd, start, &r); +if (ret <0 ) +err ("PAGEMAP_SCAN failed: %s" ,strerror (errno)); + + *categories = ret ? r.categories :0 ; +return true; +} +/* This macro let __LINE__ works in err() */ #define pagemap_check_wp (value, wp) do { \ if (!!(value & PM_UFFD_WP) != wp) \
@@ -1227,6 +1283,119 @@ static void uffd_move_pmd_test(uffd_global_test_opts_t *gopts, uffd_test_args_t uffd_move_pmd_handle_fault); } +static void uffd_move_pmd_huge_zeropage_test (uffd_global_test_opts_t *gopts, +uffd_test_args_t *targs) +{ +unsigned long pmd_size =read_pmd_pagesize (); +unsigned long pmd_pages; +unsigned long bytes = gopts->nr_pages * gopts->page_size; +char *orig_area_src = gopts->area_src, *orig_area_dst = gopts->area_dst; +char *aligned_src, *aligned_dst; +unsigned long src_offs, dst_offs, max_offs; +pthread_t uffd_mon; +struct uffd_args args = {0 }; +char c ='\0' ; +int pagemap_fd; +uint64_t categories; +unsigned long i; + +if (pmd_size <= gopts->page_size) { +uffd_test_skip ("huge page size is 0, feature missing?" ); +return ; + } +if (!detect_huge_zeropage ()) { +uffd_test_skip ("transparent huge zeropage disabled" ); +return ; + } + + pmd_pages = pmd_size / gopts->page_size; +if (bytes < pmd_size) { +uffd_test_skip ("not enough pages for one PMD-sized move" ); +return ; + } + + aligned_src =ALIGN_UP (orig_area_src, pmd_size); + aligned_dst =ALIGN_UP (orig_area_dst, pmd_size); + src_offs = (aligned_src - orig_area_src) / gopts->page_size; + dst_offs = (aligned_dst - orig_area_dst) / gopts->page_size; + max_offs = src_offs > dst_offs ? src_offs : dst_offs; +if (max_offs + pmd_pages > gopts->nr_pages) { +uffd_test_skip ("could not find aligned PMD-sized src/dst window" ); +return ; + } + +if (madvise (orig_area_dst, bytes,MADV_HUGEPAGE )) +err ("madvise(MADV_HUGEPAGE) failure" ); +if (madvise (orig_area_src, bytes,MADV_DONTFORK )) +err ("madvise(MADV_DONTFORK) failure" ); +if (madvise (aligned_src, pmd_size,MADV_DONTNEED )) +err ("madvise(MADV_DONTNEED) failure" ); + +/* Materialise a PMD-sized huge zeropage mapping in the source. */ +force_read_pages (aligned_src, pmd_pages, gopts->page_size); + + pagemap_fd =pagemap_open (); +if (!uffd_pagemap_scan_get_categories (pagemap_fd, aligned_src, &categories)) { +close (pagemap_fd); +uffd_test_skip ("PAGEMAP_SCAN unsupported" ); +return ; + } +if ((categories & (PAGE_IS_PRESENT |PAGE_IS_PFNZERO |PAGE_IS_HUGE )) != + (PAGE_IS_PRESENT |PAGE_IS_PFNZERO |PAGE_IS_HUGE )) { +close (pagemap_fd); +uffd_test_skip ("could not materialise a huge zeropage PMD mapping" ); +return ; + } + gopts->area_src = aligned_src; + gopts->area_dst = aligned_dst; + +if (uffd_register (gopts->uffd, gopts->area_dst, pmd_size, true, false, false)) +err ("register failure" ); + + args.gopts = gopts; + args.handle_fault = uffd_move_pmd_handle_fault; +if (pthread_create (&uffd_mon,NULL , uffd_poll_thread, &args)) +err ("uffd_poll_thread create" ); + +/* +* One fault on dst should trigger a single PMD-sized UFFDIO_MOVE from +* the huge zeropage PMD we populated in the source. +*/ +force_read_pages (gopts->area_dst, pmd_pages, gopts->page_size); + +if (write (gopts->pipefd[1 ], &c,sizeof (c)) !=sizeof (c)) +err ("pipe write" ); +if (pthread_join (uffd_mon,NULL )) +err ("join() failed" ); + +if (args.missing_faults !=1 || args.minor_faults !=0 ) { +uffd_test_fail ("stats check error" ); + }else if (!uffd_pagemap_scan_get_categories (pagemap_fd, gopts->area_dst, + &categories)) { +uffd_test_fail ("PAGEMAP_SCAN unsupported" ); + }else if ((categories & (PAGE_IS_PRESENT |PAGE_IS_PFNZERO | +PAGE_IS_HUGE )) != + (PAGE_IS_PRESENT |PAGE_IS_PFNZERO |PAGE_IS_HUGE )) { +uffd_test_fail ("moved destination is not a huge zeropage PMD" ); + }else if (!check_huge_anon (gopts->area_dst,0 , pmd_size)) { +/* vm_normal_page_pmd() must continue to treat the moved PMD as special. */ +uffd_test_fail ("moved huge zeropage PMD counted as AnonHugePages" ); + }else { +for (i =0 ; i < pmd_size; i++) { +if (gopts->area_dst[i]) { +uffd_test_fail ("moved huge zeropage PMD data is not zero" ); + goto out_restore; + } + } +uffd_test_pass (); + } + +out_restore: + gopts->area_src = orig_area_src; + gopts->area_dst = orig_area_dst; +close (pagemap_fd); +} +static void uffd_move_pmd_split_test (uffd_global_test_opts_t *gopts,uffd_test_args_t *targs) {if (madvise (gopts->area_dst, gopts->nr_pages * gopts->page_size,MADV_NOHUGEPAGE ))
@@ -1550,6 +1719,13 @@ uffd_test_case_t uffd_tests[] = { .uffd_feature_required = UFFD_FEATURE_MOVE, .test_case_ops = &uffd_move_test_pmd_case_ops, }, + { + .name = "move-pmd-huge-zeropage", + .uffd_fn = uffd_move_pmd_huge_zeropage_test, + .mem_targets = MEM_ANON, + .uffd_feature_required = UFFD_FEATURE_MOVE, + .test_case_ops = &uffd_move_test_pmd_case_ops, + }, { .name = "move-pmd-split", .uffd_fn = uffd_move_pmd_split_test,
Signature · 1 line
2.51.2
David Hildenbrand (Arm)
maintainerQuoted 17 lines · Chris Down
The existing uffd-unit-tests move-pmd coverage exercises PMD-sized
UFFDIO_MOVEon anonymous THPs, but it does not force the huge zeropage PMD path inmove_pages_huge_pmd().Add a dedicated anonymous
UFFDIO_MOVEPMD test that exercises this relatively comprehensively.Signed-off-by Chris Down <[email protected]>--- tools/testing/selftests/mm/uffd-unit-tests.c | 176 +++++++++++++++++++ 1 file changed, 176 insertions(+)tools/testing/selftests/mm/uffd-unit-tests.c +0
-0
tools/testing/selftests/mm/uffd-unit-tests.c static int pagemap_open(void) · line 205
Quoted 3 lines
tools/testing/selftests/mm/uffd-unit-tests.c +0
-0@@ -203,6 +203,62 @@ static int pagemap_open(void)return fd; }
Thanks for implementing this!
[...]
Quoted 5 lines
tools/testing/selftests/mm/uffd-unit-tests.c +5
-0+static bool uffd_pagemap_scan_get_categories (int fd,char *start,uint64_t *categories) +{ +struct page_region r = {0 }; +long ret; +
tools/testing/selftests/mm/uffd-unit-tests.c static int pagemap_open(void) · line 260
Quoted 10 lines
tools/testing/selftests/mm/uffd-unit-tests.c +10
-0+if (!uffd_pagemap_scan_supported (fd, start)) +return false; + + ret =uffd_pagemap_scan_get_categories_raw (fd, start, &r); +if (ret <0 ) +err ("PAGEMAP_SCAN failed: %s" ,strerror (errno)); + + *categories = ret ? r.categories :0 ; +return true; +}
Can we unify (deduplicate) that code with what we have in vm_util.c, and then only export a function like
bool pagemap_is_huge_zero(int fd, char *start)
similar to pagemap_is_softdirty() etc to be consumed by the test here?
It can simply return "false" in case the interface is not available.
Quoted 47 lines
tools/testing/selftests/mm/uffd-unit-tests.c +40
-0+/* This macro let __LINE__ works in err() */ #define pagemap_check_wp (value, wp) do { \ if (!!(value & PM_UFFD_WP) != wp) \@@ -1227,6 +1283,119 @@ static void uffd_move_pmd_test(uffd_global_test_opts_t *gopts, uffd_test_args_t uffd_move_pmd_handle_fault); } +static void uffd_move_pmd_huge_zeropage_test (uffd_global_test_opts_t *gopts, +uffd_test_args_t *targs) +{ +unsigned long pmd_size =read_pmd_pagesize (); +unsigned long pmd_pages; +unsigned long bytes = gopts->nr_pages * gopts->page_size; +char *orig_area_src = gopts->area_src, *orig_area_dst = gopts->area_dst; +char *aligned_src, *aligned_dst; +unsigned long src_offs, dst_offs, max_offs; +pthread_t uffd_mon; +struct uffd_args args = {0 }; +char c ='\0' ; +int pagemap_fd; +uint64_t categories; +unsigned long i; + +if (pmd_size <= gopts->page_size) { +uffd_test_skip ("huge page size is 0, feature missing?" ); +return ; + } +if (!detect_huge_zeropage ()) { +uffd_test_skip ("transparent huge zeropage disabled" ); +return ; + } + + pmd_pages = pmd_size / gopts->page_size; +if (bytes < pmd_size) { +uffd_test_skip ("not enough pages for one PMD-sized move" ); +return ; + } + + aligned_src =ALIGN_UP (orig_area_src, pmd_size); + aligned_dst =ALIGN_UP (orig_area_dst, pmd_size); + src_offs = (aligned_src - orig_area_src) / gopts->page_size; + dst_offs = (aligned_dst - orig_area_dst) / gopts->page_size; + max_offs = src_offs > dst_offs ? src_offs : dst_offs; +if (max_offs + pmd_pages > gopts->nr_pages) { +uffd_test_skip ("could not find aligned PMD-sized src/dst window" ); +return ;
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_move_pmd_test(uffd_global_test_opts_t *gopts, uffd_test_args_t · line 1334
Quoted 10 lines
tools/testing/selftests/mm/uffd-unit-tests.c +10
-0+ } + +if (madvise (orig_area_dst, bytes,MADV_HUGEPAGE )) +err ("madvise(MADV_HUGEPAGE) failure" ); +if (madvise (orig_area_src, bytes,MADV_DONTFORK )) +err ("madvise(MADV_DONTFORK) failure" ); +if (madvise (aligned_src, pmd_size,MADV_DONTNEED )) +err ("madvise(MADV_DONTNEED) failure" ); + +/* Materialise a PMD-sized huge zeropage mapping in the source. */
"Fault in the huge zeropage"
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_move_pmd_test(uffd_global_test_opts_t *gopts, uffd_test_args_t · line 1346
Quoted 12 lines
tools/testing/selftests/mm/uffd-unit-tests.c +12
-0+force_read_pages (aligned_src, pmd_pages, gopts->page_size); + + pagemap_fd =pagemap_open (); +if (!uffd_pagemap_scan_get_categories (pagemap_fd, aligned_src, &categories)) { +close (pagemap_fd); +uffd_test_skip ("PAGEMAP_SCAN unsupported" ); +return ; + } +if ((categories & (PAGE_IS_PRESENT |PAGE_IS_PFNZERO |PAGE_IS_HUGE )) != + (PAGE_IS_PRESENT |PAGE_IS_PFNZERO |PAGE_IS_HUGE )) { +close (pagemap_fd); +uffd_test_skip ("could not materialise a huge zeropage PMD mapping" );
"could not fault in the huge zeropage" ?
Quoted 19 lines
tools/testing/selftests/mm/uffd-unit-tests.c +19
-0+return ; + } + gopts->area_src = aligned_src; + gopts->area_dst = aligned_dst; + +if (uffd_register (gopts->uffd, gopts->area_dst, pmd_size, true, false, false)) +err ("register failure" ); + + args.gopts = gopts; + args.handle_fault = uffd_move_pmd_handle_fault; +if (pthread_create (&uffd_mon,NULL , uffd_poll_thread, &args)) +err ("uffd_poll_thread create" ); + +/* +* One fault on dst should trigger a single PMD-sized UFFDIO_MOVE from +* the huge zeropage PMD we populated in the source. +*/ +force_read_pages (gopts->area_dst, pmd_pages, gopts->page_size); +
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_move_pmd_test(uffd_global_test_opts_t *gopts, uffd_test_args_t · line 1375
Quoted 10 lines
tools/testing/selftests/mm/uffd-unit-tests.c +10
-0+if (write (gopts->pipefd[1 ], &c,sizeof (c)) !=sizeof (c)) +err ("pipe write" ); +if (pthread_join (uffd_mon,NULL )) +err ("join() failed" ); + +if (args.missing_faults !=1 || args.minor_faults !=0 ) { +uffd_test_fail ("stats check error" ); + }else if (!uffd_pagemap_scan_get_categories (pagemap_fd, gopts->area_dst, + &categories)) { +uffd_test_fail ("PAGEMAP_SCAN unsupported" );
Why should it suddenly be unsupported when it worked before?
With the pagemap_is_huge_zero() helper, this will boil down to
} else if (pagemap_is_huge_zero(...)) {
uffd_test_fail("moved destination is not a huge zeropage PMD");
}Quoted 4 lines
tools/testing/selftests/mm/uffd-unit-tests.c +4
-0+ }else if ((categories & (PAGE_IS_PRESENT |PAGE_IS_PFNZERO | +PAGE_IS_HUGE )) != + (PAGE_IS_PRESENT |PAGE_IS_PFNZERO |PAGE_IS_HUGE )) { +uffd_test_fail ("moved destination is not a huge zeropage PMD" );
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_move_pmd_test(uffd_global_test_opts_t *gopts, uffd_test_args_t · line 1389
Quoted 10 lines
tools/testing/selftests/mm/uffd-unit-tests.c +10
-0+ }else if (!check_huge_anon (gopts->area_dst,0 , pmd_size)) { +/* vm_normal_page_pmd() must continue to treat the moved PMD as special. */ +uffd_test_fail ("moved huge zeropage PMD counted as AnonHugePages" ); + }else { +for (i =0 ; i < pmd_size; i++) { +if (gopts->area_dst[i]) { +uffd_test_fail ("moved huge zeropage PMD data is not zero" ); + goto out_restore; + } + }
That's a bit excessive given that pagemap told us that it is indeed the huge zeropage. So I would just drop this.
Signature · 3 lines
Cheers, David