add anon mTHP collapse test cases
Subsystems (5)
Baolin Wang
reviewercover add anon mTHP collapse test cases
This patchset is split from the previous shmem mTHP collapse patchset[1], which only adds anon mTHP collapse selftests. All khugepaged test cases can pass.
[1] https://lore.kernel.org/all/[email protected]/
Baolin Wang (4):
selftests: mm: extend the check_huge() to support mTHP check
selftests: mm: move gather_after_split_folio_orders() into vm_util.c
file
selftests: mm: implement the mTHP-sized hugepage check helpers
selftests: mm: add mTHP collapse test cases
.../selftests/mm/folio_split_race_test.c | 2 +- tools/testing/selftests/mm/khugepaged.c | 190 +++++++++++++----- tools/testing/selftests/mm/pagemap_ioctl.c | 2 +- .../testing/selftests/mm/prctl_thp_disable.c | 2 +- tools/testing/selftests/mm/run_vmtests.sh | 2 + tools/testing/selftests/mm/soft-dirty.c | 2 +- .../selftests/mm/split_huge_page_test.c | 139 +------------ tools/testing/selftests/mm/uffd-common.c | 4 +- tools/testing/selftests/mm/vm_util.c | 180 ++++++++++++++++- tools/testing/selftests/mm/vm_util.h | 8 +- 10 files changed, 339 insertions(+), 192 deletions(-)
Signature · 1 line
2.47.3
Baolin Wang
reviewerPATCH 1/4 selftests: mm: extend the check_huge() to support mTHP check
To support checking for various sized mTHPs during mTHP collapse, extend the check_huge() function prototype to accept two new parameters specifying the address range and mTHP size, in preparation for the following patches.
No functional changes.
--- .../selftests/mm/folio_split_race_test.c | 2 +- tools/testing/selftests/mm/khugepaged.c | 66 ++++++++++--------- tools/testing/selftests/mm/pagemap_ioctl.c | 2 +- .../testing/selftests/mm/prctl_thp_disable.c | 2 +- tools/testing/selftests/mm/soft-dirty.c | 2 +- .../selftests/mm/split_huge_page_test.c | 14 ++-- tools/testing/selftests/mm/uffd-common.c | 4 +- tools/testing/selftests/mm/vm_util.c | 6 +- tools/testing/selftests/mm/vm_util.h | 6 +- 9 files changed, 56 insertions(+), 48 deletions(-)
tools/testing/selftests/mm/folio_split_race_test.c +1 -1
@@ -182,7 +182,7 @@ static uint64_t run_iteration(void)for (i =0 ; i <TOTAL_PAGES ; i++)fill_page (mmap_base, i); -if (!check_huge_shmem (mmap_base,NR_PMD_PAGE , pmd_pagesize)) +if (!check_huge_shmem (mmap_base,FILE_SIZE ,NR_PMD_PAGE , pmd_pagesize))ksft_exit_fail_msg ("No shmem THP is allocated\n" );if (pthread_barrier_init (&ctl.barrier,NULL ,NUM_READER_THREADS +1 ) !=0 )
tools/testing/selftests/mm/khugepaged.c +36 -30
238 lines not on this page · raw message
tools/testing/selftests/mm/pagemap_ioctl.c +1 -1
@@ -1085,7 +1085,7 @@ static void unpopulated_written_test(const char *name, char *mem, long size,memset (mem,1 , size);if (use_thp && (madvise (mem, size,MADV_COLLAPSE ) || - !check_huge_anon (mem, size / hpage_size, hpage_size))) { + !check_huge_anon (mem, size, size / hpage_size, hpage_size))) {ksft_test_result_skip ("%s could not form a THP\n" , name); goto out; }
tools/testing/selftests/mm/prctl_thp_disable.c +1 -1
@@ -67,7 +67,7 @@ static int test_mmap_thp(enum thp_collapse_type madvise_buf, size_t pmdsize)/* HACK: make sure we have a separate VMA that we can check reliably. */ mprotect (mem, pmdsize,PROT_READ ); - ret =check_huge_anon (mem,1 , pmdsize); + ret =check_huge_anon (mem, pmdsize,1 , pmdsize);munmap (mmap_mem, mmap_size);return ret; }
tools/testing/selftests/mm/soft-dirty.c +1 -1
@@ -103,7 +103,7 @@ static void test_hugepage(int pagemap_fd, int pagesize)for (i =0 ; i < hpage_len; i++) map[i] = (char )i; -if (check_huge_anon (map,1 , hpage_len)) { +if (check_huge_anon (map, hpage_len,1 , hpage_len)) {ksft_test_result_pass ("Test %s huge page allocation\n" , __func__);clear_softdirty ();
tools/testing/selftests/mm/split_huge_page_test.c +7 -7
@@ -296,7 +296,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hpunsigned long rss_anon_before, rss_anon_after;size_t i; -if (!check_huge_anon (one_page, nr_hpages, pmd_pagesize)) +if (!check_huge_anon (one_page, nr_hpages * pmd_pagesize, nr_hpages, pmd_pagesize))ksft_exit_fail_msg ("No THP is allocated\n" ); rss_anon_before =rss_anon ();
@@ -311,7 +311,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hpif (one_page[i] != (char )0 )ksft_exit_fail_msg ("%ld byte corrupted\n" , i); -if (!check_huge_anon (one_page,0 , pmd_pagesize)) +if (!check_huge_anon (one_page, nr_hpages * pmd_pagesize,0 , pmd_pagesize))ksft_exit_fail_msg ("Still AnonHugePages not split\n" ); rss_anon_after =rss_anon ();
@@ -347,7 +347,7 @@ static void split_pmd_thp_to_order(int order)for (i =0 ; i < len; i++) one_page[i] = (char )i; -if (!check_huge_anon (one_page,4 , pmd_pagesize)) +if (!check_huge_anon (one_page,4 * pmd_pagesize,4 , pmd_pagesize))ksft_exit_fail_msg ("No THP is allocated\n" );/* split all THPs */
@@ -366,7 +366,7 @@ static void split_pmd_thp_to_order(int order) (pmd_order + 1)))ksft_exit_fail_msg ("Unexpected THP split\n" ); -if (!check_huge_anon (one_page,0 , pmd_pagesize)) +if (!check_huge_anon (one_page,4 * pmd_pagesize,0 , pmd_pagesize))ksft_exit_fail_msg ("Still AnonHugePages not split\n" );ksft_test_result_pass ("Split huge pages to order %d successful\n" , order);
@@ -393,7 +393,7 @@ static void split_pte_mapped_thp(void)for (i =0 ; i < thp_area_size; i++) thp_area[i] = (char )i; -if (!check_huge_anon (thp_area, nr_thps, pmd_pagesize)) { +if (!check_huge_anon (thp_area, nr_thps * pmd_pagesize, nr_thps, pmd_pagesize)) {ksft_test_result_skip ("Not all THPs allocated\n" ); goto out; }
@@ -657,7 +657,7 @@ static int create_pagecache_thp_and_fd(const char *testfile, size_t fd_size,force_read_pages (*addr, fd_size / pmd_pagesize, pmd_pagesize); -if (!check_huge_file (*addr, fd_size / pmd_pagesize, pmd_pagesize)) { +if (!check_huge_file (*addr, fd_size, fd_size / pmd_pagesize, pmd_pagesize)) {ksft_print_msg ("No large pagecache folio generated, please provide a filesystem supporting large folio\n" );munmap (*addr, fd_size);close (*fd);
@@ -735,7 +735,7 @@ static void split_thp_in_pagecache_to_order_at(size_t fd_size, goto out; } -if (!check_huge_file (addr,0 , pmd_pagesize)) { +if (!check_huge_file (addr, fd_size,0 , pmd_pagesize)) {ksft_print_msg ("Still FilePmdMapped not split\n" ); err =EXIT_FAILURE ; goto out;
tools/testing/selftests/mm/uffd-common.c +3 -1
@@ -194,7 +194,9 @@ static void shmem_alias_mapping(uffd_global_test_opts_t *gopts, __u64 *start,static void shmem_check_pmd_mapping (uffd_global_test_opts_t *gopts,void *p,int expect_nr_hpages) { -if (!check_huge_shmem (gopts->area_dst_alias, expect_nr_hpages, +unsigned long size = expect_nr_hpages *read_pmd_pagesize (); + +if (!check_huge_shmem (gopts->area_dst_alias, size, expect_nr_hpages,read_pmd_pagesize ()))err ("Did not find expected %d number of hugepages" , expect_nr_hpages);
tools/testing/selftests/mm/vm_util.c +3 -3
@@ -247,17 +247,17 @@ bool __check_huge(void *addr, char *pattern, int nr_hpages,return thp == (nr_hpages * (hpage_size >>10 )); } -bool check_huge_anon (void *addr,int nr_hpages,uint64_t hpage_size) +bool check_huge_anon (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) {return __check_huge (addr,"AnonHugePages: " , nr_hpages, hpage_size); } -bool check_huge_file (void *addr,int nr_hpages,uint64_t hpage_size) +bool check_huge_file (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) {return __check_huge (addr,"FilePmdMapped:" , nr_hpages, hpage_size); } -bool check_huge_shmem (void *addr,int nr_hpages,uint64_t hpage_size) +bool check_huge_shmem (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) {return __check_huge (addr,"ShmemPmdMapped:" , nr_hpages, hpage_size); }
tools/testing/selftests/mm/vm_util.h +3 -3
@@ -90,9 +90,9 @@ void clear_softdirty(void);bool check_for_pattern (FILE *fp,const char *pattern,char *buf,size_t len);uint64_t read_pmd_pagesize (void );unsigned long rss_anon (void ); -bool check_huge_anon (void *addr,int nr_hpages,uint64_t hpage_size); -bool check_huge_file (void *addr,int nr_hpages,uint64_t hpage_size); -bool check_huge_shmem (void *addr,int nr_hpages,uint64_t hpage_size); +bool check_huge_anon (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size); +bool check_huge_file (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size); +bool check_huge_shmem (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size);int64_t allocate_transhuge (void *ptr,int pagemap_fd);int pageflags_get (unsigned long pfn,int kpageflags_fd,uint64_t *flags);
Signature · 1 line
2.47.3
Baolin Wang
reviewerPATCH 2/4 selftests: mm: move gather_after_split_folio_orders() into vm_util.c file
In reply to Baolin WangMove gather_after_split_folio_orders() to vm_util.c as a helper function in preparation for implementing checks for mTHP collapse. While we are at it, rename this function to indicate that it is not only used for large folio splits.
No functional changes.
--- .../selftests/mm/split_huge_page_test.c | 125 +----------------- tools/testing/selftests/mm/vm_util.c | 119 +++++++++++++++++ tools/testing/selftests/mm/vm_util.h | 2 + 3 files changed, 122 insertions(+), 124 deletions(-)
tools/testing/selftests/mm/split_huge_page_test.c +1 -124
@@ -104,129 +104,6 @@ static bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,return false; } -static int vaddr_pageflags_get (char *vaddr,int pagemap_fd,int kpageflags_fd, -uint64_t *flags) -{ -unsigned long pfn; - - pfn =pagemap_get_pfn (pagemap_fd, vaddr); - -/* non-present PFN */ -if (pfn ==-1UL ) -return 1 ; - -if (pageflags_get (pfn, kpageflags_fd, flags)) -return -1 ; - -return 0 ; -} - -/* -* gather_after_split_folio_orders - scan through [vaddr_start, len) and record -* folio orders -* -* @vaddr_start: start vaddr -* @len: range length -* @pagemap_fd: file descriptor to /proc/<pid>/pagemap -* @kpageflags_fd: file descriptor to /proc/kpageflags -* @orders: output folio order array -* @nr_orders: folio order array size -* -* gather_after_split_folio_orders() scan through [vaddr_start, len) and check -* all folios within the range and record their orders. All order-0 pages will -* be recorded. Non-present vaddr is skipped. -* -* NOTE: the function is used to check folio orders after a split is performed, -* so it assumes [vaddr_start, len) fully maps to after-split folios within that -* range. -* -* Return: 0 - no error, -1 - unhandled cases -*/ -static int gather_after_split_folio_orders (char *vaddr_start,size_t len, -int pagemap_fd,int kpageflags_fd,int orders[],int nr_orders) -{ -uint64_t page_flags =0 ; -int cur_order =-1 ; -char *vaddr; - -if (pagemap_fd ==-1 || kpageflags_fd ==-1 ) -return -1 ; -if (!orders) -return -1 ; -if (nr_orders <=0 ) -return -1 ; - -for (vaddr = vaddr_start; vaddr < vaddr_start + len;) { -char *next_folio_vaddr; -int status; - - status =vaddr_pageflags_get (vaddr, pagemap_fd, kpageflags_fd, - &page_flags); -if (status <0 ) -return -1 ; - -/* skip non present vaddr */ -if (status ==1 ) { - vaddr +=psize (); -continue ; - } - -/* all order-0 pages with possible false postive (non folio) */ -if (!(page_flags & (KPF_COMPOUND_HEAD |KPF_COMPOUND_TAIL ))) { - orders[0 ]++; - vaddr +=psize (); -continue ; - } - -/* skip non thp compound pages */ -if (!(page_flags &KPF_THP )) { - vaddr +=psize (); -continue ; - } - -/* vpn points to part of a THP at this point */ -if (page_flags &KPF_COMPOUND_HEAD ) - cur_order =1 ; -else { - vaddr +=psize (); -continue ; - } - - next_folio_vaddr = vaddr + (1UL << (cur_order +pshift ())); - -if (next_folio_vaddr >= vaddr_start + len) -break ; - -while ((status =vaddr_pageflags_get (next_folio_vaddr, - pagemap_fd, kpageflags_fd, - &page_flags)) >=0 ) { -/* -* non present vaddr, next compound head page, or -* order-0 page -*/ -if (status ==1 || - (page_flags &KPF_COMPOUND_HEAD ) || - !(page_flags & (KPF_COMPOUND_HEAD |KPF_COMPOUND_TAIL ))) { -if (cur_order < nr_orders) { - orders[cur_order]++; - cur_order =-1 ; - vaddr = next_folio_vaddr; - } -break ; - } - - cur_order++; - next_folio_vaddr = vaddr + (1UL << (cur_order +pshift ())); - } - -if (status <0 ) -return status; - } -if (cur_order >0 && cur_order < nr_orders) - orders[cur_order]++; -return 0 ; -} -static int check_after_split_folio_orders (char *vaddr_start,size_t len,int pagemap_fd,int kpageflags_fd,int orders[],int nr_orders) {
@@ -240,7 +117,7 @@ static int check_after_split_folio_orders(char *vaddr_start, size_t len,ksft_exit_fail_msg ("Cannot allocate memory for vaddr_orders" );memset (vaddr_orders,0 ,sizeof (int ) * nr_orders); - status =gather_after_split_folio_orders (vaddr_start, len, pagemap_fd, + status =gather_folio_orders (vaddr_start, len, pagemap_fd, kpageflags_fd, vaddr_orders, nr_orders);if (status)ksft_exit_fail_msg ("gather folio info failed\n" );
tools/testing/selftests/mm/vm_util.c +119 -0
125 lines not on this page · raw message
tools/testing/selftests/mm/vm_util.h +2 -0
@@ -95,6 +95,8 @@ bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64_t hpabool check_huge_shmem (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size);int64_t allocate_transhuge (void *ptr,int pagemap_fd);int pageflags_get (unsigned long pfn,int kpageflags_fd,uint64_t *flags); +int gather_folio_orders (char *vaddr_start,size_t len, +int pagemap_fd,int kpageflags_fd,int orders[],int nr_orders);int uffd_register (int uffd,void *addr,uint64_t len,bool miss,bool wp,bool minor);
Signature · 1 line
2.47.3
Baolin Wang
reviewerPATCH 3/4 selftests: mm: implement the mTHP-sized hugepage check helpers
In reply to Baolin WangImplement mTHP-sized hugepage checking helpers using gather_folio_orders(). Also rename the existing PMD-sized huge page check function to __check_pmd_huge() for clarity.
--- tools/testing/selftests/mm/vm_util.c | 55 ++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-)
tools/testing/selftests/mm/vm_util.c +51 -4
@@ -15,6 +15,10 @@#define SMAP_FILE_PATH "/proc/self/smaps"#define STATUS_FILE_PATH "/proc/self/status"#define MAX_LINE_LENGTH 500 +#define PAGEMAP_PATH "/proc/self/pagemap" +#define KPAGEFLAGS_PATH "/proc/kpageflags" +#define GET_ORDER (nr_pages) (31 - __builtin_clz(nr_pages)) +#define NR_ORDERS 20unsigned int __page_size;unsigned int __page_shift;
@@ -348,7 +352,7 @@ char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)return entry; } -bool __check_huge (void *addr,char *pattern,int nr_hpages, +static bool __check_pmd_huge (void *addr,char *pattern,int nr_hpages,uint64_t hpage_size) {char buffer[MAX_LINE_LENGTH ];
@@ -366,19 +370,62 @@ bool __check_huge(void *addr, char *pattern, int nr_hpages,return thp == (nr_hpages * (hpage_size >>10 )); } +static bool check_large_folios (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) +{ +int pagesize =getpagesize (); +int order =GET_ORDER (hpage_size / pagesize); +int pagemap_fd, kpageflags_fd; +int orders[NR_ORDERS ], status; +bool ret = false; + +memset (orders,0 ,sizeof (int ) *NR_ORDERS ); + + pagemap_fd =open (PAGEMAP_PATH ,O_RDONLY ); +if (pagemap_fd ==-1 ) +ksft_exit_fail_msg ("read pagemap fail\n" ); + + kpageflags_fd =open (KPAGEFLAGS_PATH ,O_RDONLY ); +if (kpageflags_fd ==-1 ) { +close (pagemap_fd); +ksft_exit_fail_msg ("read kpageflags fail\n" ); + } + + status =gather_folio_orders (addr, size, pagemap_fd, + kpageflags_fd, orders,NR_ORDERS ); +if (status) + goto out; + +if (orders[order] == nr_hpages) + ret = true; + +out: +close (pagemap_fd); +close (kpageflags_fd); +return ret; +} +bool check_huge_anon (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) { -return __check_huge (addr,"AnonHugePages: " , nr_hpages, hpage_size); +if (hpage_size ==read_pmd_pagesize ()) +return __check_pmd_huge (addr,"AnonHugePages: " , nr_hpages, hpage_size); + +return check_large_folios (addr, size, nr_hpages, hpage_size); }bool check_huge_file (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) { -return __check_huge (addr,"FilePmdMapped:" , nr_hpages, hpage_size); +if (hpage_size ==read_pmd_pagesize ()) +return __check_pmd_huge (addr,"FilePmdMapped:" , nr_hpages, hpage_size); + +return check_large_folios (addr, size, nr_hpages, hpage_size); }bool check_huge_shmem (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) { -return __check_huge (addr,"ShmemPmdMapped:" , nr_hpages, hpage_size); +if (hpage_size ==read_pmd_pagesize ()) +return __check_pmd_huge (addr,"ShmemPmdMapped:" , nr_hpages, hpage_size); + +return check_large_folios (addr, size, nr_hpages, hpage_size); }int64_t allocate_transhuge (void *ptr,int pagemap_fd)
Signature · 1 line
2.47.3
Baolin Wang
reviewerRe: [PATCH 3/4] selftests: mm: implement the mTHP-sized hugepage check helpers
Quoted 14 lines · Baolin Wang
Implement mTHP-sized hugepage checking helpers using
gather_folio_orders(). Also rename the existing PMD-sized huge page check function to__check_pmd_huge()for clarity.Signed-off-by Baolin Wang <[email protected]>--- tools/testing/selftests/mm/vm_util.c | 55 ++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-)tools/testing/selftests/mm/vm_util.c +0
-0
tools/testing/selftests/mm/vm_util.c line 20
Quoted 6 lines
tools/testing/selftests/mm/vm_util.c +3
-0@@ -15,6 +15,10 @@#define SMAP_FILE_PATH "/proc/self/smaps"#define STATUS_FILE_PATH "/proc/self/status"#define MAX_LINE_LENGTH 500 +#define PAGEMAP_PATH "/proc/self/pagemap" +#define KPAGEFLAGS_PATH "/proc/kpageflags" +#define GET_ORDER (nr_pages) (31 - __builtin_clz(nr_pages))
sashiko comment:
"Is there a risk of undefined behavior here if nr_pages evaluates to 0?
If check_large_folios() is called with an hpage_size smaller than the system page size, the division hpage_size / pagesize will yield 0. Calling __builtin_clz(0) results in undefined behavior."
This doesn't happen now. But for code robustness, I'll add a hpage_size check in check_large_folios().
Quoted 34 lines
tools/testing/selftests/mm/vm_util.c +19
-1+#define NR_ORDERS 20unsigned int __page_size;unsigned int __page_shift;@@ -348,7 +352,7 @@ char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)return entry; } -bool __check_huge (void *addr,char *pattern,int nr_hpages, +static bool __check_pmd_huge (void *addr,char *pattern,int nr_hpages,uint64_t hpage_size) {char buffer[MAX_LINE_LENGTH ];@@ -366,19 +370,62 @@ bool __check_huge(void *addr, char *pattern, int nr_hpages,return thp == (nr_hpages * (hpage_size >>10 )); } +static bool check_large_folios (void *addr,unsigned long size,int nr_hpages,uint64_t hpage_size) +{ +int pagesize =getpagesize (); +int order =GET_ORDER (hpage_size / pagesize); +int pagemap_fd, kpageflags_fd; +int orders[NR_ORDERS ], status; +bool ret = false; + +memset (orders,0 ,sizeof (int ) *NR_ORDERS ); + + pagemap_fd =open (PAGEMAP_PATH ,O_RDONLY ); +if (pagemap_fd ==-1 ) +ksft_exit_fail_msg ("read pagemap fail\n" ); + + kpageflags_fd =open (KPAGEFLAGS_PATH ,O_RDONLY ); +if (kpageflags_fd ==-1 ) { +close (pagemap_fd);
tools/testing/selftests/mm/vm_util.c bool __check_huge(void *addr, char *pattern, int nr_hpages, · line 399
Quoted 10 lines
tools/testing/selftests/mm/vm_util.c +10
-0+ksft_exit_fail_msg ("read kpageflags fail\n" ); + } + + status =gather_folio_orders (addr, size, pagemap_fd, + kpageflags_fd, orders,NR_ORDERS ); +if (status) + goto out; + +if (orders[order] == nr_hpages) + ret = true;
sashiko comment:
"Does this code overflow the orders[] stack array if the calculated order is 20 or greater?
For example, on architectures supporting very large huge pages (like 16GB huge pages on PowerPC), the order could be 22. It looks like indexing orders[order] here without bounds checking could cause an out-of-bounds stack read."
This doesn't look like the mTHP order size, but I'll add an order check.
Baolin Wang
reviewerPATCH 4/4 selftests: mm: add mTHP collapse test cases
In reply to Baolin WangAdded a new command 'mthp_khugepaged' for mTHP collapse, along with the '-c' parameter to specify the collapse order. Additionally, added mTHP collapse test cases for 'collapse_full', 'collapse_empty', and 'collapse_single_mthp' for anonymous folios. All khugepaged test cases passed.
--- tools/testing/selftests/mm/khugepaged.c | 130 ++++++++++++++++++---- tools/testing/selftests/mm/run_vmtests.sh | 2 + 2 files changed, 113 insertions(+), 19 deletions(-)
tools/testing/selftests/mm/khugepaged.c +111 -19
@@ -26,9 +26,11 @@#define BASE_ADDR ((void *)(1UL << 30))static unsigned long hpage_pmd_size; +static int hpage_pmd_order;static unsigned long page_size;static int hpage_pmd_nr;static int anon_order; +static int collapse_order;#define PID_SMAPS "/proc/self/smaps"#define TEST_FILE "collapse_test_file"
@@ -69,6 +71,7 @@ struct collapse_context { };static struct collapse_context *khugepaged_context; +static struct collapse_context *mthp_khugepaged_context;static struct collapse_context *madvise_context;struct file_info {
@@ -554,25 +557,25 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages, }#define TICK 500000 -static bool wait_for_scan (const char *msg,char *p,int nr_hpages, -struct mem_ops *ops) +static bool wait_for_scan (const char *msg,char *p,unsigned long size, +int nr_hpages,int collap_order,struct mem_ops *ops) { -unsigned long size = nr_hpages * hpage_pmd_size; +unsigned long hpage_size = page_size << collap_order;int full_scans;int timeout =6 ;/* 3 seconds */ /* Sanity check */ -if (!ops->check_huge (p, size,0 , hpage_pmd_size)) +if (!ops->check_huge (p, size,0 , hpage_size))ksft_exit_fail_msg ("Unexpected huge page\n" ); -madvise (p, nr_hpages * hpage_pmd_size,MADV_HUGEPAGE ); +madvise (p, size,MADV_HUGEPAGE );/* Wait until the second full_scan completed */ full_scans =thp_read_num ("khugepaged/full_scans" ) +2 ;ksft_print_msg ("%s..." , msg);while (timeout--) { -if (ops->check_huge (p, size, nr_hpages, hpage_pmd_size)) +if (ops->check_huge (p, size, nr_hpages, hpage_size))break ;if (thp_read_num ("khugepaged/full_scans" ) >= full_scans)break ;
@@ -595,7 +598,7 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,if (!is_tmpfs (ops) && ops == &__read_write_file_write_ops) expect = false; -if (wait_for_scan (msg, p, nr_hpages, ops)) { +if (wait_for_scan (msg, p, size, nr_hpages, hpage_pmd_order, ops)) {if (expect)fail ("Timeout" );else
@@ -617,12 +620,62 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,fail ("Fail" ); } +static void mthp_khugepaged_collapse (const char *msg,char *p,int nr_hpages, +struct mem_ops *ops,bool expect) +{ +unsigned long hpage_size = page_size << collapse_order; +struct thp_settings settings = *thp_current_settings (); +/* mTHP collpase only allocates PMD sized memory */ +unsigned long size = hpage_pmd_size; + +/* Set mTHP setting for mTHP collapse */ +if (ops == &__anon_ops) { + settings.thp_enabled =THP_NEVER ; + settings.hugepages[collapse_order].enabled =THP_ALWAYS ; + } + +thp_push_settings (&settings); + +if (wait_for_scan (msg, p, size, nr_hpages, collapse_order, ops)) { +if (expect) +fail ("Timeout" ); +else +success ("OK" ); + +/* Restore THP settings for mTHP collapse. */ +thp_pop_settings (); +return ; + } + +/* +* For file and shmem memory, khugepaged only retracts pte entries after +* putting the new hugepage in the page cache. The hugepage must be +* subsequently refaulted to install the pmd mapping for the mm. +*/ +if (ops != &__anon_ops) + ops->fault (p,0 , nr_hpages * hpage_size); + +if (ops->check_huge (p, size, expect ? nr_hpages :0 , hpage_size)) +success ("OK" ); +else +fail ("Fail" ); + +/* Restore THP settings for mTHP collapse. */ +thp_pop_settings (); +} +static struct collapse_context __khugepaged_context = { .collapse = &khugepaged_collapse, .enforce_pte_scan_limits = true, .name ="khugepaged" , }; +static struct collapse_context __mthp_khugepaged_context = { + .collapse = &mthp_khugepaged_collapse, + .enforce_pte_scan_limits = true, + .name ="mthp_khugepaged" , +}; +static struct collapse_context __madvise_context = { .collapse = &madvise_collapse, .enforce_pte_scan_limits = false,
@@ -661,10 +714,17 @@ static void alloc_at_fault(void)static void collapse_full (struct collapse_context *c,struct mem_ops *ops) {void *p; -int nr_hpages =4 ; +int nr_pmds =4 , nr_hpages =4 ;unsigned long size = nr_hpages * hpage_pmd_size; - p = ops->setup_area (nr_hpages); +/* Only try 1 PMD sized range for mTHP collapse. */ +if (c == &__mthp_khugepaged_context) { + nr_pmds =1 ; + nr_hpages =1 << (hpage_pmd_order - collapse_order); + size = hpage_pmd_size; + } + + p = ops->setup_area (nr_pmds); ops->fault (p,0 , size); c->collapse ("Collapse multiple fully populated PTE table" , p, nr_hpages, ops, true);
@@ -676,10 +736,31 @@ static void collapse_full(struct collapse_context *c, struct mem_ops *ops)static void collapse_empty (struct collapse_context *c,struct mem_ops *ops) { +int nr_hpages =1 ; +void *p; + +if (c == &__mthp_khugepaged_context) + nr_hpages =1 << (hpage_pmd_order - collapse_order); + + p = ops->setup_area (1 ); + c->collapse ("Do not collapse empty PTE table" , p, nr_hpages, ops, false); + ops->cleanup_area (p, hpage_pmd_size); +ksft_test_result_report (exit_status,"%s\n" , __func__); +} + +static void collapse_single_mthp (struct collapse_context *c,struct mem_ops *ops) +{ +unsigned long hpage_size = page_size << collapse_order;void *p; p = ops->setup_area (1 ); - c->collapse ("Do not collapse empty PTE table" , p,1 , ops, false); +/* +* Only fault collapse_order sized ranges, and only check 1 +* collapse_order sized huge page. +*/ + ops->fault (p,0 , hpage_size); + c->collapse ("Collapse PTE table with half PTE entries present" , + p,1 , ops, true); ops->cleanup_area (p, hpage_pmd_size);ksft_test_result_report (exit_status,"%s\n" , __func__); }
@@ -1081,8 +1162,8 @@ static void madvise_retracted_page_tables(struct collapse_context *c, ops->fault (p,0 , size);/* Let khugepaged collapse and leave pmd cleared */ -if (wait_for_scan ("Collapse and leave PMD cleared" , p, nr_hpages, - ops)) { +if (wait_for_scan ("Collapse and leave PMD cleared" , p, size, nr_hpages, + hpage_pmd_order, ops)) {fail ("Timeout" );return ; }
@@ -1098,7 +1179,7 @@ static void usage(void) {fprintf (stderr,"\nUsage: ./khugepaged [OPTIONS] <test type> [dir]\n\n" );fprintf (stderr,"\t<test type>\t: <context>:<mem_type>\n" ); -fprintf (stderr,"\t<context>\t: [all|khugepaged|madvise]\n" ); +fprintf (stderr,"\t<context>\t: [all|khugepaged|mthp_khugepaged|madvise]\n" );fprintf (stderr,"\t<mem_type>\t: [all|anon|file|shmem]\n" );fprintf (stderr,"\n\t\"file,all\" mem_type requires [dir] argument\n" );fprintf (stderr,"\n\t\"file,all\" mem_type requires a file system\n" );
@@ -1109,6 +1190,7 @@ static void usage(void)fprintf (stderr,"\t\t-h: This help message.\n" );fprintf (stderr,"\t\t-s: mTHP size, expressed as page order.\n" );fprintf (stderr,"\t\t Defaults to 0. Use this size for anon or shmem allocations.\n" ); +fprintf (stderr,"\t\t-c: collapse order for mTHP collapse, expressed as page order.\n" );exit (1 ); }
@@ -1118,11 +1200,14 @@ static void parse_test_type(int argc, char **argv)char *buf;const char *token; -while ((opt =getopt (argc, argv,"s:h" )) !=-1 ) { +while ((opt =getopt (argc, argv,"s:c:h" )) !=-1 ) {switch (opt) {case 's' : anon_order =atoi (optarg);break ; +case 'c' : + collapse_order =atoi (optarg); +break ;case 'h' :default :usage ();
@@ -1148,6 +1233,10 @@ static void parse_test_type(int argc, char **argv) madvise_context = &__madvise_context; }else if (!strcmp (token,"khugepaged" )) { khugepaged_context = &__khugepaged_context; + }else if (!strcmp (token,"mthp_khugepaged" )) { + mthp_khugepaged_context = &__mthp_khugepaged_context; +if (collapse_order <=0 || collapse_order >= hpage_pmd_order) +usage (); }else if (!strcmp (token,"madvise" )) { madvise_context = &__madvise_context; }else {
@@ -1213,7 +1302,6 @@ static int nr_test_cases;int main (int argc,char **argv) { -int hpage_pmd_order;struct thp_settings default_settings = { .thp_enabled =THP_MADVISE , .thp_defrag =THP_DEFRAG_ALWAYS ,
@@ -1239,10 +1327,6 @@ int main(int argc, char **argv)if (!thp_is_enabled ())ksft_exit_skip ("Transparent Hugepages not available\n" ); -parse_test_type (argc, argv); - -setbuf (stdout,NULL ); - page_size =getpagesize (); hpage_pmd_size =read_pmd_pagesize ();if (!hpage_pmd_size)
@@ -1250,6 +1334,10 @@ int main(int argc, char **argv) hpage_pmd_nr = hpage_pmd_size / page_size; hpage_pmd_order =__builtin_ctz (hpage_pmd_nr); +parse_test_type (argc, argv); + +setbuf (stdout,NULL ); + default_settings.khugepaged.max_ptes_none = hpage_pmd_nr -1 ; default_settings.khugepaged.max_ptes_swap = hpage_pmd_nr /8 ; default_settings.khugepaged.max_ptes_shared = hpage_pmd_nr /2 ;
@@ -1267,6 +1355,7 @@ int main(int argc, char **argv)TEST (collapse_full, khugepaged_context, read_write_file_read_ops);TEST (collapse_full, khugepaged_context, read_write_file_write_ops);TEST (collapse_full, khugepaged_context, shmem_ops); +TEST (collapse_full, mthp_khugepaged_context, anon_ops);TEST (collapse_full, madvise_context, anon_ops);TEST (collapse_full, madvise_context, read_only_file_ops);TEST (collapse_full, madvise_context, read_write_file_read_ops);
@@ -1274,8 +1363,11 @@ int main(int argc, char **argv)TEST (collapse_full, madvise_context, shmem_ops);TEST (collapse_empty, khugepaged_context, anon_ops); +TEST (collapse_empty, mthp_khugepaged_context, anon_ops);TEST (collapse_empty, madvise_context, anon_ops); +TEST (collapse_single_mthp, mthp_khugepaged_context, anon_ops); +TEST (collapse_single_pte_entry, khugepaged_context, anon_ops);TEST (collapse_single_pte_entry, khugepaged_context, read_only_file_ops);TEST (collapse_single_pte_entry, khugepaged_context, read_write_file_read_ops);
tools/testing/selftests/mm/run_vmtests.sh +2 -0
@@ -410,6 +410,8 @@ CATEGORY="thp" run_test ./khugepaged all:shmem CATEGORY="thp" run_test ./khugepaged -s 4 all:shmem +CATEGORY="thp" run_test ./khugepaged -c 4 mthp_khugepaged:anon + # Try to create XFS if not provided if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then if test_selected "thp"; then
Signature · 1 line
2.47.3
Baolin Wang
reviewerRe: [PATCH 4/4] selftests: mm: add mTHP collapse test cases
Quoted 87 lines · Baolin Wang
Added a new command '
mthp_khugepaged' for mTHP collapse, along with the '-c' parameter to specify the collapse order. Additionally, added mTHP collapse test cases for 'collapse_full', 'collapse_empty', and 'collapse_single_mthp' for anonymous folios. All khugepaged test cases passed.Signed-off-by Baolin Wang <[email protected]>--- tools/testing/selftests/mm/khugepaged.c | 130 ++++++++++++++++++---- tools/testing/selftests/mm/run_vmtests.sh | 2 + 2 files changed, 113 insertions(+), 19 deletions(-)tools/testing/selftests/mm/khugepaged.c +17
-7@@ -26,9 +26,11 @@#define BASE_ADDR ((void *)(1UL << 30))static unsigned long hpage_pmd_size; +static int hpage_pmd_order;static unsigned long page_size;static int hpage_pmd_nr;static int anon_order; +static int collapse_order;#define PID_SMAPS "/proc/self/smaps"#define TEST_FILE "collapse_test_file"@@ -69,6 +71,7 @@ struct collapse_context { };static struct collapse_context *khugepaged_context; +static struct collapse_context *mthp_khugepaged_context;static struct collapse_context *madvise_context;struct file_info {@@ -554,25 +557,25 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages, }#define TICK 500000 -static bool wait_for_scan (const char *msg,char *p,int nr_hpages, -struct mem_ops *ops) +static bool wait_for_scan (const char *msg,char *p,unsigned long size, +int nr_hpages,int collap_order,struct mem_ops *ops) { -unsigned long size = nr_hpages * hpage_pmd_size; +unsigned long hpage_size = page_size << collap_order;int full_scans;int timeout =6 ;/* 3 seconds */ /* Sanity check */ -if (!ops->check_huge (p, size,0 , hpage_pmd_size)) +if (!ops->check_huge (p, size,0 , hpage_size))ksft_exit_fail_msg ("Unexpected huge page\n" ); -madvise (p, nr_hpages * hpage_pmd_size,MADV_HUGEPAGE ); +madvise (p, size,MADV_HUGEPAGE );/* Wait until the second full_scan completed */ full_scans =thp_read_num ("khugepaged/full_scans" ) +2 ;ksft_print_msg ("%s..." , msg);while (timeout--) { -if (ops->check_huge (p, size, nr_hpages, hpage_pmd_size)) +if (ops->check_huge (p, size, nr_hpages, hpage_size))break ;if (thp_read_num ("khugepaged/full_scans" ) >= full_scans)break ;@@ -595,7 +598,7 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,if (!is_tmpfs (ops) && ops == &__read_write_file_write_ops) expect = false; -if (wait_for_scan (msg, p, nr_hpages, ops)) { +if (wait_for_scan (msg, p, size, nr_hpages, hpage_pmd_order, ops)) {if (expect)fail ("Timeout" );else @@ -617,12 +620,62 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,fail ("Fail" ); } +static void mthp_khugepaged_collapse (const char *msg,char *p,int nr_hpages, +struct mem_ops *ops,bool expect) +{ +unsigned long hpage_size = page_size << collapse_order; +struct thp_settings settings = *thp_current_settings (); +/* mTHP collpase only allocates PMD sized memory */ +unsigned long size = hpage_pmd_size;
tools/testing/selftests/mm/khugepaged.c static void khugepaged_collapse(const char *msg, char *p, int nr_hpages, · line 639
Quoted 10 lines
tools/testing/selftests/mm/khugepaged.c +10
-0+ +/* Set mTHP setting for mTHP collapse */ +if (ops == &__anon_ops) { + settings.thp_enabled =THP_NEVER ; + settings.hugepages[collapse_order].enabled =THP_ALWAYS ; + } + +thp_push_settings (&settings); + +if (wait_for_scan (msg, p, size, nr_hpages, collapse_order, ops)) {
sashiko said:
"Could this setup create a race condition that leads to spurious test failures?
In mthp_khugepaged_collapse(), thp_push_settings() is called to set hugepages[collapse_order].enabled = THP_ALWAYS before wait_for_scan() is invoked. The khugepaged thread is then immediately allowed to scan and
collapse the faulted memory.
The very first action in wait_for_scan() is a sanity check to verify that there are currently zero huge pages:
tools/testing/selftests/mm/khugepaged.c:wait_for_scan() {
...
/* Sanity check */
if (!ops->check_huge(p, size, 0, hpage_size))
ksft_exit_fail_msg("Unexpected huge page\n");
...
}If khugepaged collapses the memory in the brief window between thp_push_settings() and this sanity check, will the test falsely fail with an unexpected huge page error? For configurations relying on THP_MADVISE, this race doesn't appear to occur because the madvise(..., MADV_HUGEPAGE) call happens after the sanity check in wait_for_scan()."
Although this race is unlikely to happen (from my testing, it has never occurred), setting it to THP_MADVISE is indeed more reasonable. Will do in the next version.