linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/4] Better split_huge_page_test result check
@ 2025-08-12 15:55 Zi Yan
  2025-08-12 15:55 ` [PATCH v3 1/4] mm/huge_memory: add new_order and offset to split_huge_pages*() pr_debug Zi Yan
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Zi Yan @ 2025-08-12 15:55 UTC (permalink / raw)
  To: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm
  Cc: Andrew Morton, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

This patchset uses kpageflags to get after-split folio orders for a better
split_huge_page_test result check[1]. The added gather_folio_orders() scans
through a VPN range and collects the numbers of folios at different orders.
check_folio_orders() compares the result of gather_folio_orders() to
a given list of numbers of different orders.

This patchset also added new order and in folio offset to the split huge
page debugfs's pr_debug()s;

Changelog
===
From V2[3]:
1. Added two missing free()s in check_folio_orders().
2. Reimplemented is_backed_by_thp() to use kpageflags to get precise
   folio order information and renamed it to is_backed_by_folio() in new
   Patch 3.
3. Renamed *_file to *_fd in Patch 2.
4. Indentation fixes.
5. Fixed vaddr stepping issue in gather_folio_orders() when a compound
   tail page is encountered.
6. Used pmd_order in place of max_order in split_huge_page_test.c.
7. Documented gather_folio_orders().

From V1[2]:
1. Dropped split_huge_pages_pid() for loop step change to avoid messing
   up with PTE-mapped THP handling. split_huge_page_test.c is changed to
   perform split at [addr, addr + pagesize) range to limit one
   folio_split() per folio.
2. Moved pr_debug changes in Patch 2 to Patch 1.
3. Moved KPF_* to vm_util.h and used PAGEMAP_PFN instead of local PFN_MASK.
4. Used pagemap_get_pfn() helper.
5. Used char *vaddr and size_t len as inputs to gather_folio_orders() and
   check_folio_orders() instead of vpn and nr_pages.
6. Removed variable length variables and used malloc instead.

[1] https://lore.kernel.org/linux-mm/e2f32bdb-e4a4-447c-867c-31405cbba151@redhat.com/
[2] https://lore.kernel.org/linux-mm/20250806022045.342824-1-ziy@nvidia.com/
[3] https://lore.kernel.org/linux-mm/20250808190144.797076-1-ziy@nvidia.com/

Zi Yan (4):
  mm/huge_memory: add new_order and offset to split_huge_pages*()
    pr_debug.
  selftests/mm: add check_folio_orders() helper.
  selftests/mm: reimplement is_backed_by_thp() with more precise check
  selftests/mm: check after-split folio orders in split_huge_page_test.

 mm/huge_memory.c                              |   8 +-
 .../selftests/mm/split_huge_page_test.c       | 154 +++++++++++-----
 tools/testing/selftests/mm/vm_util.c          | 173 ++++++++++++++++++
 tools/testing/selftests/mm/vm_util.h          |   8 +
 4 files changed, 292 insertions(+), 51 deletions(-)

-- 
2.47.2


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v3 1/4] mm/huge_memory: add new_order and offset to split_huge_pages*() pr_debug.
  2025-08-12 15:55 [PATCH v3 0/4] Better split_huge_page_test result check Zi Yan
@ 2025-08-12 15:55 ` Zi Yan
  2025-08-12 15:55 ` [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper Zi Yan
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 13+ messages in thread
From: Zi Yan @ 2025-08-12 15:55 UTC (permalink / raw)
  To: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm
  Cc: Andrew Morton, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest, Donet Tom

They are useful information for debugging split huge page tests.

Signed-off-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Wei Yang <richard.weiyang@gmail.com>
Reviewed-by: Donet Tom <donettom@linux.ibm.com>
Reviewed-by: wang lian <lianux.mm@gmail.com>
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Reviewed-by: Barry Song <baohua@kernel.org>
Acked-by: David Hildenbrand <david@redhat.com>
---
 mm/huge_memory.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index b8bb078a1a34..22dc727d1d27 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4319,8 +4319,8 @@ static int split_huge_pages_pid(int pid, unsigned long vaddr_start,
 		goto out;
 	}
 
-	pr_debug("Split huge pages in pid: %d, vaddr: [0x%lx - 0x%lx]\n",
-		 pid, vaddr_start, vaddr_end);
+	pr_debug("Split huge pages in pid: %d, vaddr: [0x%lx - 0x%lx], new_order: %u, in_folio_offset: %ld\n",
+		 pid, vaddr_start, vaddr_end, new_order, in_folio_offset);
 
 	mmap_read_lock(mm);
 	/*
@@ -4430,8 +4430,8 @@ static int split_huge_pages_in_file(const char *file_path, pgoff_t off_start,
 	if (IS_ERR(candidate))
 		goto out;
 
-	pr_debug("split file-backed THPs in file: %s, page offset: [0x%lx - 0x%lx]\n",
-		 file_path, off_start, off_end);
+	pr_debug("split file-backed THPs in file: %s, page offset: [0x%lx - 0x%lx], new_order: %u, in_folio_offset: %ld\n",
+		 file_path, off_start, off_end, new_order, in_folio_offset);
 
 	mapping = candidate->f_mapping;
 	min_order = mapping_min_folio_order(mapping);
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper.
  2025-08-12 15:55 [PATCH v3 0/4] Better split_huge_page_test result check Zi Yan
  2025-08-12 15:55 ` [PATCH v3 1/4] mm/huge_memory: add new_order and offset to split_huge_pages*() pr_debug Zi Yan
@ 2025-08-12 15:55 ` Zi Yan
  2025-08-13  3:38   ` wang lian
  2025-08-13 21:12   ` Wei Yang
  2025-08-12 15:55 ` [PATCH v3 3/4] selftests/mm: reimplement is_backed_by_thp() with more precise check Zi Yan
  2025-08-12 15:55 ` [PATCH v3 4/4] selftests/mm: check after-split folio orders in split_huge_page_test Zi Yan
  3 siblings, 2 replies; 13+ messages in thread
From: Zi Yan @ 2025-08-12 15:55 UTC (permalink / raw)
  To: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm
  Cc: Andrew Morton, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

The helper gathers an folio order statistics of folios within a virtual
address range and checks it against a given order list. It aims to provide
a more precise folio order check instead of just checking the existence of
PMD folios.

Signed-off-by: Zi Yan <ziy@nvidia.com>
---
 .../selftests/mm/split_huge_page_test.c       |   4 +-
 tools/testing/selftests/mm/vm_util.c          | 173 ++++++++++++++++++
 tools/testing/selftests/mm/vm_util.h          |   7 +
 3 files changed, 181 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 5d07b0b89226..63ac82f0b9e0 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -34,8 +34,6 @@ uint64_t pmd_pagesize;
 #define PID_FMT_OFFSET "%d,0x%lx,0x%lx,%d,%d"
 #define PATH_FMT "%s,0x%lx,0x%lx,%d"
 
-#define PFN_MASK     ((1UL<<55)-1)
-#define KPF_THP      (1UL<<22)
 #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
 
 int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
@@ -49,7 +47,7 @@ int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
 
 		if (kpageflags_file) {
 			pread(kpageflags_file, &page_flags, sizeof(page_flags),
-				(paddr & PFN_MASK) * sizeof(page_flags));
+				PAGEMAP_PFN(paddr) * sizeof(page_flags));
 
 			return !!(page_flags & KPF_THP);
 		}
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 6a239aa413e2..4d952d1bc96d 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -338,6 +338,179 @@ int detect_hugetlb_page_sizes(size_t sizes[], int max)
 	return count;
 }
 
+static int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
+{
+	size_t count;
+
+	count = pread(kpageflags_fd, flags, sizeof(*flags),
+		      pfn * sizeof(*flags));
+
+	if (count != sizeof(*flags))
+		return -1;
+
+	return 0;
+}
+
+static int get_page_flags(char *vaddr, int pagemap_fd, int kpageflags_fd,
+			  uint64_t *flags)
+{
+	unsigned long pfn;
+
+	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
+	/*
+	 * Treat non-present page as a page without any flag, so that
+	 * gather_folio_orders() just record the current folio order.
+	 */
+	if (pfn == -1UL) {
+		*flags = 0;
+		return 1;
+	}
+
+	if (get_pfn_flags(pfn, kpageflags_fd, flags))
+		return -1;
+
+	return 0;
+}
+
+/*
+ * gather_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_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.
+ *
+ *
+ * Return: 0 - no error, -1 - unhandled cases
+ */
+static int gather_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 || !kpageflags_fd)
+		return -1;
+	if (nr_orders <= 0)
+		return -1;
+
+	for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
+		char *next_folio_vaddr;
+		int status;
+
+		status = get_page_flags(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 {
+			/* not a head nor a tail in a THP? */
+			if (!(page_flags & KPF_COMPOUND_TAIL))
+				return -1;
+
+			vaddr += psize();
+			continue;
+		}
+
+		next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
+
+		if (next_folio_vaddr >= vaddr_start + len)
+			break;
+
+		while ((status = get_page_flags(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;
+			}
+
+			/* not a head nor a tail in a THP? */
+			if (!(page_flags & KPF_COMPOUND_TAIL))
+				return -1;
+
+			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;
+}
+
+int check_folio_orders(char *vaddr_start, size_t len, int pagemap_fd,
+			int kpageflags_fd, int orders[], int nr_orders)
+{
+	int *vaddr_orders;
+	int status;
+	int i;
+
+	vaddr_orders = (int *)malloc(sizeof(int) * nr_orders);
+
+	if (!vaddr_orders)
+		ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders");
+
+	memset(vaddr_orders, 0, sizeof(int) * nr_orders);
+	status = gather_folio_orders(vaddr_start, len, pagemap_fd,
+				     kpageflags_fd, vaddr_orders, nr_orders);
+	if (status)
+		goto out;
+
+	status = 0;
+	for (i = 0; i < nr_orders; i++)
+		if (vaddr_orders[i] != orders[i]) {
+			ksft_print_msg("order %d: expected: %d got %d\n", i,
+				       orders[i], vaddr_orders[i]);
+			status = -1;
+		}
+
+out:
+	free(vaddr_orders);
+	return status;
+}
+
 /* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */
 int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len,
 			      bool miss, bool wp, bool minor, uint64_t *ioctls)
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 1843ad48d32b..02e3f1e7065b 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -18,6 +18,11 @@
 #define PM_SWAP                       BIT_ULL(62)
 #define PM_PRESENT                    BIT_ULL(63)
 
+#define KPF_COMPOUND_HEAD             BIT_ULL(15)
+#define KPF_COMPOUND_TAIL             BIT_ULL(16)
+#define KPF_THP                       BIT_ULL(22)
+
+
 /*
  * Ignore the checkpatch warning, we must read from x but don't want to do
  * anything with it in order to trigger a read page fault. We therefore must use
@@ -85,6 +90,8 @@ bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size);
 int64_t allocate_transhuge(void *ptr, int pagemap_fd);
 unsigned long default_huge_page_size(void);
 int detect_hugetlb_page_sizes(size_t sizes[], int max);
+int check_folio_orders(char *vaddr_start, size_t len, int pagemap_file,
+			int kpageflags_file, int orders[], int nr_orders);
 
 int uffd_register(int uffd, void *addr, uint64_t len,
 		  bool miss, bool wp, bool minor);
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 3/4] selftests/mm: reimplement is_backed_by_thp() with more precise check
  2025-08-12 15:55 [PATCH v3 0/4] Better split_huge_page_test result check Zi Yan
  2025-08-12 15:55 ` [PATCH v3 1/4] mm/huge_memory: add new_order and offset to split_huge_pages*() pr_debug Zi Yan
  2025-08-12 15:55 ` [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper Zi Yan
@ 2025-08-12 15:55 ` Zi Yan
  2025-08-13 21:41   ` Wei Yang
  2025-08-12 15:55 ` [PATCH v3 4/4] selftests/mm: check after-split folio orders in split_huge_page_test Zi Yan
  3 siblings, 1 reply; 13+ messages in thread
From: Zi Yan @ 2025-08-12 15:55 UTC (permalink / raw)
  To: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm
  Cc: Andrew Morton, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

and rename it to is_backed_by_folio().

is_backed_by_folio() checks if the given vaddr is backed a folio with
a given order. It does so by:
1. getting the pfn of the vaddr;
2. checking kpageflags of the pfn;

if order is greater than 0:
3. checking kpageflags of the head pfn;
4. checking kpageflags of all tail pfns.

pmd_order is added to split_huge_page_test.c and replaces max_order.

Signed-off-by: Zi Yan <ziy@nvidia.com>
---
 .../selftests/mm/split_huge_page_test.c       | 67 +++++++++++++------
 tools/testing/selftests/mm/vm_util.c          |  2 +-
 tools/testing/selftests/mm/vm_util.h          |  1 +
 3 files changed, 48 insertions(+), 22 deletions(-)

diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 63ac82f0b9e0..3aaf783f339f 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -25,6 +25,7 @@
 uint64_t pagesize;
 unsigned int pageshift;
 uint64_t pmd_pagesize;
+unsigned int pmd_order;
 
 #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
 #define SMAP_PATH "/proc/self/smaps"
@@ -36,23 +37,48 @@ uint64_t pmd_pagesize;
 
 #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
 
-int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
+int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd)
 {
-	uint64_t paddr;
-	uint64_t page_flags;
+	unsigned long pfn_head;
+	uint64_t pfn_flags;
+	unsigned long pfn;
+	unsigned long i;
 
-	if (pagemap_file) {
-		pread(pagemap_file, &paddr, sizeof(paddr),
-			((long)vaddr >> pageshift) * sizeof(paddr));
+	if (!pagemap_fd || !kpageflags_fd)
+		return 0;
 
-		if (kpageflags_file) {
-			pread(kpageflags_file, &page_flags, sizeof(page_flags),
-				PAGEMAP_PFN(paddr) * sizeof(page_flags));
+	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
 
-			return !!(page_flags & KPF_THP);
-		}
+	if (pfn == -1UL)
+		return 0;
+
+	if (get_pfn_flags(pfn, kpageflags_fd, &pfn_flags))
+		return 0;
+
+	if (!order) {
+		if (pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))
+			return 0;
+		return 1;
 	}
-	return 0;
+
+	if (!(pfn_flags & KPF_THP))
+		return 0;
+
+	pfn_head = pfn & ~((1 << order) - 1);
+
+	if (get_pfn_flags(pfn_head, kpageflags_fd, &pfn_flags))
+		return 0;
+
+	if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD)))
+		return 0;
+
+	for (i = 1; i < (1UL << order) - 1; i++) {
+		if (get_pfn_flags(pfn_head + i, kpageflags_fd, &pfn_flags))
+			return 0;
+		if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_TAIL)))
+			return 0;
+	}
+	return 1;
 }
 
 static void write_file(const char *path, const char *buf, size_t buflen)
@@ -233,7 +259,7 @@ void split_pte_mapped_thp(void)
 	thp_size = 0;
 	for (i = 0; i < pagesize * 4; i++)
 		if (i % pagesize == 0 &&
-		    is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
+		    is_backed_by_folio(&pte_mapped[i], pmd_order, pagemap_fd, kpageflags_fd))
 			thp_size++;
 
 	if (thp_size != 4)
@@ -250,7 +276,7 @@ void split_pte_mapped_thp(void)
 			ksft_exit_fail_msg("%ld byte corrupted\n", i);
 
 		if (i % pagesize == 0 &&
-		    is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
+		    !is_backed_by_folio(&pte_mapped[i], 0, pagemap_fd, kpageflags_fd))
 			thp_size++;
 	}
 
@@ -522,7 +548,6 @@ int main(int argc, char **argv)
 	const char *fs_loc;
 	bool created_tmp;
 	int offset;
-	unsigned int max_order;
 	unsigned int nr_pages;
 	unsigned int tests;
 
@@ -543,28 +568,28 @@ int main(int argc, char **argv)
 		ksft_exit_fail_msg("Reading PMD pagesize failed\n");
 
 	nr_pages = pmd_pagesize / pagesize;
-	max_order = GET_ORDER(nr_pages);
-	tests = 2 + (max_order - 1) + (2 * max_order) + (max_order - 1) * 4 + 2;
+	pmd_order = GET_ORDER(nr_pages);
+	tests = 2 + (pmd_order - 1) + (2 * pmd_order) + (pmd_order - 1) * 4 + 2;
 	ksft_set_plan(tests);
 
 	fd_size = 2 * pmd_pagesize;
 
 	split_pmd_zero_pages();
 
-	for (i = 0; i < max_order; i++)
+	for (i = 0; i < pmd_order; i++)
 		if (i != 1)
 			split_pmd_thp_to_order(i);
 
 	split_pte_mapped_thp();
-	for (i = 0; i < max_order; i++)
+	for (i = 0; i < pmd_order; i++)
 		split_file_backed_thp(i);
 
 	created_tmp = prepare_thp_fs(optional_xfs_path, fs_loc_template,
 			&fs_loc);
-	for (i = max_order - 1; i >= 0; i--)
+	for (i = pmd_order - 1; i >= 0; i--)
 		split_thp_in_pagecache_to_order_at(fd_size, fs_loc, i, -1);
 
-	for (i = 0; i < max_order; i++)
+	for (i = 0; i < pmd_order; i++)
 		for (offset = 0;
 		     offset < nr_pages;
 		     offset += MAX(nr_pages / 4, 1 << i))
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 4d952d1bc96d..193ba1a1a3cc 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -338,7 +338,7 @@ int detect_hugetlb_page_sizes(size_t sizes[], int max)
 	return count;
 }
 
-static int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
+int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
 {
 	size_t count;
 
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 02e3f1e7065b..148b792cff0f 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -92,6 +92,7 @@ unsigned long default_huge_page_size(void);
 int detect_hugetlb_page_sizes(size_t sizes[], int max);
 int check_folio_orders(char *vaddr_start, size_t len, int pagemap_file,
 			int kpageflags_file, int orders[], int nr_orders);
+int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags);
 
 int uffd_register(int uffd, void *addr, uint64_t len,
 		  bool miss, bool wp, bool minor);
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 4/4] selftests/mm: check after-split folio orders in split_huge_page_test.
  2025-08-12 15:55 [PATCH v3 0/4] Better split_huge_page_test result check Zi Yan
                   ` (2 preceding siblings ...)
  2025-08-12 15:55 ` [PATCH v3 3/4] selftests/mm: reimplement is_backed_by_thp() with more precise check Zi Yan
@ 2025-08-12 15:55 ` Zi Yan
  2025-08-14  9:16   ` Wei Yang
  3 siblings, 1 reply; 13+ messages in thread
From: Zi Yan @ 2025-08-12 15:55 UTC (permalink / raw)
  To: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm
  Cc: Andrew Morton, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

Instead of just checking the existence of PMD folios before and after folio
split tests, use check_folio_orders() to check after-split folio orders.

The following tests are not changed:
1. split_pte_mapped_thp: the test already uses kpageflags to check;
2. split_file_backed_thp: no vaddr available.

Signed-off-by: Zi Yan <ziy@nvidia.com>
---
 .../selftests/mm/split_huge_page_test.c       | 85 +++++++++++++------
 1 file changed, 61 insertions(+), 24 deletions(-)

diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 3aaf783f339f..1ea2c7f22962 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -26,6 +26,7 @@ uint64_t pagesize;
 unsigned int pageshift;
 uint64_t pmd_pagesize;
 unsigned int pmd_order;
+int *expected_orders;
 
 #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
 #define SMAP_PATH "/proc/self/smaps"
@@ -37,6 +38,11 @@ unsigned int pmd_order;
 
 #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
 
+const char *pagemap_proc = "/proc/self/pagemap";
+const char *kpageflags_proc = "/proc/kpageflags";
+int pagemap_fd;
+int kpageflags_fd;
+
 int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd)
 {
 	unsigned long pfn_head;
@@ -49,18 +55,21 @@ int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd
 
 	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
 
+	/* non present page */
 	if (pfn == -1UL)
 		return 0;
 
 	if (get_pfn_flags(pfn, kpageflags_fd, &pfn_flags))
 		return 0;
 
+	/* check for order-0 pages */
 	if (!order) {
 		if (pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))
 			return 0;
 		return 1;
 	}
 
+	/* non THP folio */
 	if (!(pfn_flags & KPF_THP))
 		return 0;
 
@@ -69,9 +78,11 @@ int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd
 	if (get_pfn_flags(pfn_head, kpageflags_fd, &pfn_flags))
 		return 0;
 
+	/* head PFN has no compound_head flag set */
 	if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD)))
 		return 0;
 
+	/* check all tail PFN flags */
 	for (i = 1; i < (1UL << order) - 1; i++) {
 		if (get_pfn_flags(pfn_head + i, kpageflags_fd, &pfn_flags))
 			return 0;
@@ -198,6 +209,12 @@ void split_pmd_thp_to_order(int order)
 		if (one_page[i] != (char)i)
 			ksft_exit_fail_msg("%ld byte corrupted\n", i);
 
+	memset(expected_orders, 0, sizeof(int) * (pmd_order + 1));
+	expected_orders[order] = 4 << (pmd_order - order);
+
+	if (check_folio_orders(one_page, len, pagemap_fd, kpageflags_fd,
+			       expected_orders, (pmd_order + 1)))
+		ksft_exit_fail_msg("Unexpected THP split\n");
 
 	if (!check_huge_anon(one_page, 0, pmd_pagesize))
 		ksft_exit_fail_msg("Still AnonHugePages not split\n");
@@ -212,22 +229,6 @@ void split_pte_mapped_thp(void)
 	size_t len = 4 * pmd_pagesize;
 	uint64_t thp_size;
 	size_t i;
-	const char *pagemap_template = "/proc/%d/pagemap";
-	const char *kpageflags_proc = "/proc/kpageflags";
-	char pagemap_proc[255];
-	int pagemap_fd;
-	int kpageflags_fd;
-
-	if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0)
-		ksft_exit_fail_msg("get pagemap proc error: %s\n", strerror(errno));
-
-	pagemap_fd = open(pagemap_proc, O_RDONLY);
-	if (pagemap_fd == -1)
-		ksft_exit_fail_msg("read pagemap: %s\n", strerror(errno));
-
-	kpageflags_fd = open(kpageflags_proc, O_RDONLY);
-	if (kpageflags_fd == -1)
-		ksft_exit_fail_msg("read kpageflags: %s\n", strerror(errno));
 
 	one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE,
 			MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
@@ -285,8 +286,6 @@ void split_pte_mapped_thp(void)
 
 	ksft_test_result_pass("Split PTE-mapped huge pages successful\n");
 	munmap(one_page, len);
-	close(pagemap_fd);
-	close(kpageflags_fd);
 }
 
 void split_file_backed_thp(int order)
@@ -489,6 +488,7 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
 		int order, int offset)
 {
 	int fd;
+	char *split_addr;
 	char *addr;
 	size_t i;
 	char testfile[INPUT_MAX];
@@ -502,14 +502,27 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
 	err = create_pagecache_thp_and_fd(testfile, fd_size, &fd, &addr);
 	if (err)
 		return;
+
 	err = 0;
 
-	if (offset == -1)
-		write_debugfs(PID_FMT, getpid(), (uint64_t)addr,
-			      (uint64_t)addr + fd_size, order);
-	else
-		write_debugfs(PID_FMT_OFFSET, getpid(), (uint64_t)addr,
-			      (uint64_t)addr + fd_size, order, offset);
+	memset(expected_orders, 0, sizeof(int) * (pmd_order + 1));
+	if (offset == -1) {
+		for (split_addr = addr; split_addr < addr + fd_size; split_addr += pmd_pagesize)
+			write_debugfs(PID_FMT, getpid(), (uint64_t)split_addr,
+				      (uint64_t)split_addr + pagesize, order);
+
+		expected_orders[order] = fd_size / (pagesize << order);
+	} else {
+		int times = fd_size / pmd_pagesize;
+
+		for (split_addr = addr; split_addr < addr + fd_size; split_addr += pmd_pagesize)
+			write_debugfs(PID_FMT_OFFSET, getpid(), (uint64_t)split_addr,
+				      (uint64_t)split_addr + pagesize, order, offset);
+
+		for (i = order + 1; i < pmd_order; i++)
+			expected_orders[i] = times;
+		expected_orders[order] = 2 * times;
+	}
 
 	for (i = 0; i < fd_size; i++)
 		if (*(addr + i) != (char)i) {
@@ -518,6 +531,13 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
 			goto out;
 		}
 
+	if (check_folio_orders(addr, fd_size, pagemap_fd, kpageflags_fd,
+			       expected_orders, (pmd_order + 1))) {
+		ksft_print_msg("Unexpected THP split\n");
+		err = 1;
+		goto out;
+	}
+
 	if (!check_huge_file(addr, 0, pmd_pagesize)) {
 		ksft_print_msg("Still FilePmdMapped not split\n");
 		err = EXIT_FAILURE;
@@ -569,9 +589,22 @@ int main(int argc, char **argv)
 
 	nr_pages = pmd_pagesize / pagesize;
 	pmd_order = GET_ORDER(nr_pages);
+
+	expected_orders = (int *)malloc(sizeof(int) * (pmd_order + 1));
+	if (!expected_orders)
+		ksft_exit_fail_msg("Fail to allocate memory: %s\n", strerror(errno));
+
 	tests = 2 + (pmd_order - 1) + (2 * pmd_order) + (pmd_order - 1) * 4 + 2;
 	ksft_set_plan(tests);
 
+	pagemap_fd = open(pagemap_proc, O_RDONLY);
+	if (pagemap_fd == -1)
+		ksft_exit_fail_msg("read pagemap: %s\n", strerror(errno));
+
+	kpageflags_fd = open(kpageflags_proc, O_RDONLY);
+	if (kpageflags_fd == -1)
+		ksft_exit_fail_msg("read kpageflags: %s\n", strerror(errno));
+
 	fd_size = 2 * pmd_pagesize;
 
 	split_pmd_zero_pages();
@@ -596,6 +629,10 @@ int main(int argc, char **argv)
 			split_thp_in_pagecache_to_order_at(fd_size, fs_loc, i, offset);
 	cleanup_thp_fs(fs_loc, created_tmp);
 
+	close(pagemap_fd);
+	close(kpageflags_fd);
+	free(expected_orders);
+
 	ksft_finished();
 
 	return 0;
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper.
  2025-08-12 15:55 ` [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper Zi Yan
@ 2025-08-13  3:38   ` wang lian
  2025-08-14 17:50     ` Zi Yan
  2025-08-13 21:12   ` Wei Yang
  1 sibling, 1 reply; 13+ messages in thread
From: wang lian @ 2025-08-13  3:38 UTC (permalink / raw)
  To: Zi Yan
  Cc: Wei Yang, Baolin Wang, David Hildenbrand, linux-mm, Andrew Morton,
	Lorenzo Stoakes, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, linux-kernel,
	linux-kselftest



> On Aug 12, 2025, at 23:55, Zi Yan <ziy@nvidia.com> wrote:
> 
> The helper gathers an folio order statistics of folios within a virtual
> address range and checks it against a given order list. It aims to provide
> a more precise folio order check instead of just checking the existence of
> PMD folios.
> 
> Signed-off-by: Zi Yan <ziy@nvidia.com>
> ---
> .../selftests/mm/split_huge_page_test.c       |   4 +-
> tools/testing/selftests/mm/vm_util.c          | 173 ++++++++++++++++++
> tools/testing/selftests/mm/vm_util.h          |   7 +
> 3 files changed, 181 insertions(+), 3 deletions(-)
> 
> diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
> index 5d07b0b89226..63ac82f0b9e0 100644
> --- a/tools/testing/selftests/mm/split_huge_page_test.c
> +++ b/tools/testing/selftests/mm/split_huge_page_test.c
> @@ -34,8 +34,6 @@ uint64_t pmd_pagesize;
> #define PID_FMT_OFFSET "%d,0x%lx,0x%lx,%d,%d"
> #define PATH_FMT "%s,0x%lx,0x%lx,%d"
> 
> -#define PFN_MASK     ((1UL<<55)-1)
> -#define KPF_THP      (1UL<<22)
> #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
> 
> int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
> @@ -49,7 +47,7 @@ int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
> 
> 		if (kpageflags_file) {
> 			pread(kpageflags_file, &page_flags, sizeof(page_flags),
> -				(paddr & PFN_MASK) * sizeof(page_flags));
> +				PAGEMAP_PFN(paddr) * sizeof(page_flags));
> 
> 			return !!(page_flags & KPF_THP);
> 		}
> diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
> index 6a239aa413e2..4d952d1bc96d 100644
> --- a/tools/testing/selftests/mm/vm_util.c
> +++ b/tools/testing/selftests/mm/vm_util.c
> @@ -338,6 +338,179 @@ int detect_hugetlb_page_sizes(size_t sizes[], int max)
> 	return count;
> }
> 
> +static int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
> +{
> +	size_t count;
> +
> +	count = pread(kpageflags_fd, flags, sizeof(*flags),
> +		      pfn * sizeof(*flags));
> +
> +	if (count != sizeof(*flags))
> +		return -1;
> +
> +	return 0;
> +}
> +
> +static int get_page_flags(char *vaddr, int pagemap_fd, int kpageflags_fd,
> +			  uint64_t *flags)
> +{
> +	unsigned long pfn;
> +
> +	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
> +	/*
> +	 * Treat non-present page as a page without any flag, so that
> +	 * gather_folio_orders() just record the current folio order.
> +	 */
> +	if (pfn == -1UL) {
> +		*flags = 0;
> +		return 1;
> +	}
> +
> +	if (get_pfn_flags(pfn, kpageflags_fd, flags))
> +		return -1;
> +
> +	return 0;
> +}
> +
> +/*
> + * gather_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_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.
> + *
> + *
> + * Return: 0 - no error, -1 - unhandled cases
> + */
> +static int gather_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 || !kpageflags_fd)
> +		return -1;
> +	if (nr_orders <= 0)
> +		return -1;
> +
> +	for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
> +		char *next_folio_vaddr;
> +		int status;
> +
> +		status = get_page_flags(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 {
> +			/* not a head nor a tail in a THP? */
> +			if (!(page_flags & KPF_COMPOUND_TAIL))
> +				return -1;
> +
> +			vaddr += psize();
> +			continue;
> +		}
> +
> +		next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
> +
> +		if (next_folio_vaddr >= vaddr_start + len)
> +			break;
> +
> +		while ((status = get_page_flags(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;
> +			}
> +
> +			/* not a head nor a tail in a THP? */
> +			if (!(page_flags & KPF_COMPOUND_TAIL))
> +				return -1;
> +
> +			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;
> +}
> +
> +int check_folio_orders(char *vaddr_start, size_t len, int pagemap_fd,
> +			int kpageflags_fd, int orders[], int nr_orders)
> +{
> +	int *vaddr_orders;
> +	int status;
> +	int i;
> +
> +	vaddr_orders = (int *)malloc(sizeof(int) * nr_orders);
> +
> +	if (!vaddr_orders)
> +		ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders");
> +
> +	memset(vaddr_orders, 0, sizeof(int) * nr_orders);
> +	status = gather_folio_orders(vaddr_start, len, pagemap_fd,
> +				     kpageflags_fd, vaddr_orders, nr_orders);
> +	if (status)
> +		goto out;
> +
> +	status = 0;

Nit.
It seems redundant.
Would you consider removing it for a bit more conciseness?
This doesn't block my approval, of course.
Reviewed-by: wang lian <lianux.mm@gmail.com>

> +	for (i = 0; i < nr_orders; i++)
> +		if (vaddr_orders[i] != orders[i]) {
> +			ksft_print_msg("order %d: expected: %d got %d\n", i,
> +				       orders[i], vaddr_orders[i]);
> +			status = -1;
> +		}
> +
> +out:
> +	free(vaddr_orders);
> +	return status;
> +}
> +
> /* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */
> int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len,
> 			      bool miss, bool wp, bool minor, uint64_t *ioctls)
> diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
> index 1843ad48d32b..02e3f1e7065b 100644
> --- a/tools/testing/selftests/mm/vm_util.h
> +++ b/tools/testing/selftests/mm/vm_util.h
> @@ -18,6 +18,11 @@
> #define PM_SWAP                       BIT_ULL(62)
> #define PM_PRESENT                    BIT_ULL(63)
> 
> +#define KPF_COMPOUND_HEAD             BIT_ULL(15)
> +#define KPF_COMPOUND_TAIL             BIT_ULL(16)
> +#define KPF_THP                       BIT_ULL(22)
> +
> +
> /*
>  * Ignore the checkpatch warning, we must read from x but don't want to do
>  * anything with it in order to trigger a read page fault. We therefore must use
> @@ -85,6 +90,8 @@ bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size);
> int64_t allocate_transhuge(void *ptr, int pagemap_fd);
> unsigned long default_huge_page_size(void);
> int detect_hugetlb_page_sizes(size_t sizes[], int max);
> +int check_folio_orders(char *vaddr_start, size_t len, int pagemap_file,
> +			int kpageflags_file, int orders[], int nr_orders);
> 
> int uffd_register(int uffd, void *addr, uint64_t len,
> 		  bool miss, bool wp, bool minor);
> -- 
> 2.47.2
> 

Best regards,
wang lian


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper.
  2025-08-12 15:55 ` [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper Zi Yan
  2025-08-13  3:38   ` wang lian
@ 2025-08-13 21:12   ` Wei Yang
  2025-08-13 21:56     ` Zi Yan
  1 sibling, 1 reply; 13+ messages in thread
From: Wei Yang @ 2025-08-13 21:12 UTC (permalink / raw)
  To: Zi Yan
  Cc: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

On Tue, Aug 12, 2025 at 11:55:10AM -0400, Zi Yan wrote:
[...]
>+/*
>+ * gather_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_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.

I feel a little confused about the description here. Especially on the
behavior when the range is not aligned on folio boundary. 

See following code at 1) and 2).

>+ * Non-present vaddr is skipped.
>+ *
>+ *
>+ * Return: 0 - no error, -1 - unhandled cases
>+ */
>+static int gather_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 || !kpageflags_fd)
>+		return -1;

If my understanding is correct, we use open() to get a file descriptor.

On error it returns -1. And 0 is a possible valid value, but usually used by
stdin. The code may work in most cases, but seems not right.

>+	if (nr_orders <= 0)
>+		return -1;
>+

Maybe we want to check orders[] here too?

>+	for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
>+		char *next_folio_vaddr;
>+		int status;
>+
>+		status = get_page_flags(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) */

Do we still false positive case? Non-present page returns 1, which is handled
above.

>+		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 {
>+			/* not a head nor a tail in a THP? */
>+			if (!(page_flags & KPF_COMPOUND_TAIL))
>+				return -1;

When reaches here, we know (page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL)).
So we have at least one of it set.

Looks not possible to hit it?

>+
>+			vaddr += psize();
>+			continue;

1)

In case vaddr points to the middle of a large folio, this will skip this folio
and count from next one.

>+		}
>+
>+		next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
>+
>+		if (next_folio_vaddr >= vaddr_start + len)
>+			break;
>+
>+		while ((status = get_page_flags(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;
>+			}
>+
>+			/* not a head nor a tail in a THP? */
>+			if (!(page_flags & KPF_COMPOUND_TAIL))
>+				return -1;
>+
>+			cur_order++;
>+			next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));

2)

If (vaddr_start + len) points to the middle of a large folio and folio is more
than order 1 size, we may continue the loop and still count this last folio.
Because we don't check next_folio_vaddr and (vaddr_start + len).

A simple chart of these case.

          vaddr_start                   +     len
               |                               |
               v                               v
     +---------------------+              +-----------------+
     |folio 1              |              |folio 2          |
     +---------------------+              +-----------------+

folio 1 is not counted, but folio 2 is counted.

So at 1) and 2) handles the boundary differently. Not sure this is designed
behavior. If so I think it would be better to record in document, otherwise
the behavior is not obvious to user.

>+		}
>+
>+		if (status < 0)
>+			return status;
>+	}
>+	if (cur_order > 0 && cur_order < nr_orders)
>+		orders[cur_order]++;

Another boundary case here.

If we come here because (next_folio_vaddr >= vaddr_start + len) in the for
loop instead of the while loop. This means we found the folio head at vaddr,
but the left range (vaddr_start + len - vaddr) is less than or equal to order
1 page size.

But we haven't detected the real end of this folio. If this folio is more than
order 1 size, we still count it an order 1 folio.

>+	return 0;
>+}
>+
>+int check_folio_orders(char *vaddr_start, size_t len, int pagemap_fd,
>+			int kpageflags_fd, int orders[], int nr_orders)
>+{
>+	int *vaddr_orders;
>+	int status;
>+	int i;
>+
>+	vaddr_orders = (int *)malloc(sizeof(int) * nr_orders);
>+
>+	if (!vaddr_orders)
>+		ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders");
>+
>+	memset(vaddr_orders, 0, sizeof(int) * nr_orders);
>+	status = gather_folio_orders(vaddr_start, len, pagemap_fd,
>+				     kpageflags_fd, vaddr_orders, nr_orders);
>+	if (status)
>+		goto out;
>+
>+	status = 0;
>+	for (i = 0; i < nr_orders; i++)
>+		if (vaddr_orders[i] != orders[i]) {
>+			ksft_print_msg("order %d: expected: %d got %d\n", i,
>+				       orders[i], vaddr_orders[i]);
>+			status = -1;
>+		}
>+
>+out:
>+	free(vaddr_orders);
>+	return status;
>+}

-- 
Wei Yang
Help you, Help me

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 3/4] selftests/mm: reimplement is_backed_by_thp() with more precise check
  2025-08-12 15:55 ` [PATCH v3 3/4] selftests/mm: reimplement is_backed_by_thp() with more precise check Zi Yan
@ 2025-08-13 21:41   ` Wei Yang
  2025-08-13 21:58     ` Zi Yan
  0 siblings, 1 reply; 13+ messages in thread
From: Wei Yang @ 2025-08-13 21:41 UTC (permalink / raw)
  To: Zi Yan
  Cc: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

On Tue, Aug 12, 2025 at 11:55:11AM -0400, Zi Yan wrote:
>and rename it to is_backed_by_folio().
>
>is_backed_by_folio() checks if the given vaddr is backed a folio with
>a given order. It does so by:
>1. getting the pfn of the vaddr;
>2. checking kpageflags of the pfn;
>
>if order is greater than 0:
>3. checking kpageflags of the head pfn;
>4. checking kpageflags of all tail pfns.
>
>pmd_order is added to split_huge_page_test.c and replaces max_order.
>
>Signed-off-by: Zi Yan <ziy@nvidia.com>
>---
> .../selftests/mm/split_huge_page_test.c       | 67 +++++++++++++------
> tools/testing/selftests/mm/vm_util.c          |  2 +-
> tools/testing/selftests/mm/vm_util.h          |  1 +
> 3 files changed, 48 insertions(+), 22 deletions(-)
>
>diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
>index 63ac82f0b9e0..3aaf783f339f 100644
>--- a/tools/testing/selftests/mm/split_huge_page_test.c
>+++ b/tools/testing/selftests/mm/split_huge_page_test.c
>@@ -25,6 +25,7 @@
> uint64_t pagesize;
> unsigned int pageshift;
> uint64_t pmd_pagesize;
>+unsigned int pmd_order;
> 
> #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
> #define SMAP_PATH "/proc/self/smaps"
>@@ -36,23 +37,48 @@ uint64_t pmd_pagesize;
> 
> #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
> 
>-int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
>+int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd)
> {
>-	uint64_t paddr;
>-	uint64_t page_flags;
>+	unsigned long pfn_head;
>+	uint64_t pfn_flags;
>+	unsigned long pfn;
>+	unsigned long i;
> 
>-	if (pagemap_file) {
>-		pread(pagemap_file, &paddr, sizeof(paddr),
>-			((long)vaddr >> pageshift) * sizeof(paddr));
>+	if (!pagemap_fd || !kpageflags_fd)
>+		return 0;

The same in patch 2.

> 
>-		if (kpageflags_file) {
>-			pread(kpageflags_file, &page_flags, sizeof(page_flags),
>-				PAGEMAP_PFN(paddr) * sizeof(page_flags));
>+	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
> 
>-			return !!(page_flags & KPF_THP);
>-		}
>+	if (pfn == -1UL)
>+		return 0;
>+
>+	if (get_pfn_flags(pfn, kpageflags_fd, &pfn_flags))
>+		return 0;
>+
>+	if (!order) {
>+		if (pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))
>+			return 0;
>+		return 1;
> 	}
>-	return 0;
>+
>+	if (!(pfn_flags & KPF_THP))
>+		return 0;
>+
>+	pfn_head = pfn & ~((1 << order) - 1);
>+
>+	if (get_pfn_flags(pfn_head, kpageflags_fd, &pfn_flags))
>+		return 0;
>+
>+	if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD)))
>+		return 0;
>+
>+	for (i = 1; i < (1UL << order) - 1; i++) {

Do we miss the last tail?

>+		if (get_pfn_flags(pfn_head + i, kpageflags_fd, &pfn_flags))
>+			return 0;
>+		if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_TAIL)))
>+			return 0;
>+	}

If this folio is larger than order, would it still return 1?

>+	return 1;
> }
> 
> static void write_file(const char *path, const char *buf, size_t buflen)
>@@ -233,7 +259,7 @@ void split_pte_mapped_thp(void)
> 	thp_size = 0;
> 	for (i = 0; i < pagesize * 4; i++)
> 		if (i % pagesize == 0 &&
>-		    is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
>+		    is_backed_by_folio(&pte_mapped[i], pmd_order, pagemap_fd, kpageflags_fd))
> 			thp_size++;
> 
> 	if (thp_size != 4)
>@@ -250,7 +276,7 @@ void split_pte_mapped_thp(void)
> 			ksft_exit_fail_msg("%ld byte corrupted\n", i);
> 
> 		if (i % pagesize == 0 &&
>-		    is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
>+		    !is_backed_by_folio(&pte_mapped[i], 0, pagemap_fd, kpageflags_fd))
> 			thp_size++;
> 	}
> 
>@@ -522,7 +548,6 @@ int main(int argc, char **argv)
> 	const char *fs_loc;
> 	bool created_tmp;
> 	int offset;
>-	unsigned int max_order;
> 	unsigned int nr_pages;
> 	unsigned int tests;
> 
>@@ -543,28 +568,28 @@ int main(int argc, char **argv)
> 		ksft_exit_fail_msg("Reading PMD pagesize failed\n");
> 
> 	nr_pages = pmd_pagesize / pagesize;
>-	max_order = GET_ORDER(nr_pages);
>-	tests = 2 + (max_order - 1) + (2 * max_order) + (max_order - 1) * 4 + 2;
>+	pmd_order = GET_ORDER(nr_pages);
>+	tests = 2 + (pmd_order - 1) + (2 * pmd_order) + (pmd_order - 1) * 4 + 2;
> 	ksft_set_plan(tests);
> 
> 	fd_size = 2 * pmd_pagesize;
> 
> 	split_pmd_zero_pages();
> 
>-	for (i = 0; i < max_order; i++)
>+	for (i = 0; i < pmd_order; i++)
> 		if (i != 1)
> 			split_pmd_thp_to_order(i);
> 
> 	split_pte_mapped_thp();
>-	for (i = 0; i < max_order; i++)
>+	for (i = 0; i < pmd_order; i++)
> 		split_file_backed_thp(i);
> 
> 	created_tmp = prepare_thp_fs(optional_xfs_path, fs_loc_template,
> 			&fs_loc);
>-	for (i = max_order - 1; i >= 0; i--)
>+	for (i = pmd_order - 1; i >= 0; i--)
> 		split_thp_in_pagecache_to_order_at(fd_size, fs_loc, i, -1);
> 
>-	for (i = 0; i < max_order; i++)
>+	for (i = 0; i < pmd_order; i++)
> 		for (offset = 0;
> 		     offset < nr_pages;
> 		     offset += MAX(nr_pages / 4, 1 << i))
>diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
>index 4d952d1bc96d..193ba1a1a3cc 100644
>--- a/tools/testing/selftests/mm/vm_util.c
>+++ b/tools/testing/selftests/mm/vm_util.c
>@@ -338,7 +338,7 @@ int detect_hugetlb_page_sizes(size_t sizes[], int max)
> 	return count;
> }
> 
>-static int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
>+int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
> {
> 	size_t count;
> 
>diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
>index 02e3f1e7065b..148b792cff0f 100644
>--- a/tools/testing/selftests/mm/vm_util.h
>+++ b/tools/testing/selftests/mm/vm_util.h
>@@ -92,6 +92,7 @@ unsigned long default_huge_page_size(void);
> int detect_hugetlb_page_sizes(size_t sizes[], int max);
> int check_folio_orders(char *vaddr_start, size_t len, int pagemap_file,
> 			int kpageflags_file, int orders[], int nr_orders);
>+int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags);
> 
> int uffd_register(int uffd, void *addr, uint64_t len,
> 		  bool miss, bool wp, bool minor);
>-- 
>2.47.2

-- 
Wei Yang
Help you, Help me

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper.
  2025-08-13 21:12   ` Wei Yang
@ 2025-08-13 21:56     ` Zi Yan
  0 siblings, 0 replies; 13+ messages in thread
From: Zi Yan @ 2025-08-13 21:56 UTC (permalink / raw)
  To: Wei Yang
  Cc: wang lian, Baolin Wang, David Hildenbrand, linux-mm,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

On 13 Aug 2025, at 17:12, Wei Yang wrote:

> On Tue, Aug 12, 2025 at 11:55:10AM -0400, Zi Yan wrote:
> [...]
>> +/*
>> + * gather_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_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.
>
> I feel a little confused about the description here. Especially on the
> behavior when the range is not aligned on folio boundary.

I was too ambitious on this function. It is intended to just check after
split folio orders. I will move the function to split_huge_page_test.c
and rename it to gather_after_split_folio_orders() and
check_after_split_folio_orders().

>
> See following code at 1) and 2).
>
>> + * Non-present vaddr is skipped.
>> + *
>> + *
>> + * Return: 0 - no error, -1 - unhandled cases
>> + */
>> +static int gather_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 || !kpageflags_fd)
>> +		return -1;
>
> If my understanding is correct, we use open() to get a file descriptor.
>
> On error it returns -1. And 0 is a possible valid value, but usually used by
> stdin. The code may work in most cases, but seems not right.

Will fix it to

if (pagemap_fd == -1 || kpageflags_fd == -1)

>
>> +	if (nr_orders <= 0)
>> +		return -1;
>> +
>
> Maybe we want to check orders[] here too?
>
>> +	for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
>> +		char *next_folio_vaddr;
>> +		int status;
>> +
>> +		status = get_page_flags(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) */
>
> Do we still false positive case? Non-present page returns 1, which is handled
> above.

Any order-0 non folio will be counted, like GFP_KERNEL pages.

>
>> +		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 {
>> +			/* not a head nor a tail in a THP? */
>> +			if (!(page_flags & KPF_COMPOUND_TAIL))
>> +				return -1;
>
> When reaches here, we know (page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL)).
> So we have at least one of it set.
>
> Looks not possible to hit it?

Will remove it.
>
>> +
>> +			vaddr += psize();
>> +			continue;
>
> 1)
>
> In case vaddr points to the middle of a large folio, this will skip this folio
> and count from next one.
>
>> +		}
>> +
>> +		next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
>> +
>> +		if (next_folio_vaddr >= vaddr_start + len)
>> +			break;
>> +
>> +		while ((status = get_page_flags(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;
>> +			}
>> +
>> +			/* not a head nor a tail in a THP? */
>> +			if (!(page_flags & KPF_COMPOUND_TAIL))
>> +				return -1;
>> +
>> +			cur_order++;
>> +			next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
>
> 2)
>
> If (vaddr_start + len) points to the middle of a large folio and folio is more
> than order 1 size, we may continue the loop and still count this last folio.
> Because we don't check next_folio_vaddr and (vaddr_start + len).
>
> A simple chart of these case.
>
>           vaddr_start                   +     len
>                |                               |
>                v                               v
>      +---------------------+              +-----------------+
>      |folio 1              |              |folio 2          |
>      +---------------------+              +-----------------+
>
> folio 1 is not counted, but folio 2 is counted.
>
> So at 1) and 2) handles the boundary differently. Not sure this is designed
> behavior. If so I think it would be better to record in document, otherwise
> the behavior is not obvious to user.

Will document it.

>
>> +		}
>> +
>> +		if (status < 0)
>> +			return status;
>> +	}
>> +	if (cur_order > 0 && cur_order < nr_orders)
>> +		orders[cur_order]++;
>
> Another boundary case here.
>
> If we come here because (next_folio_vaddr >= vaddr_start + len) in the for
> loop instead of the while loop. This means we found the folio head at vaddr,
> but the left range (vaddr_start + len - vaddr) is less than or equal to order
> 1 page size.
>
> But we haven't detected the real end of this folio. If this folio is more than
> order 1 size, we still count it an order 1 folio.

Yes. Will document it.

Thanks for the review.

>
>> +	return 0;
>> +}
>> +
>> +int check_folio_orders(char *vaddr_start, size_t len, int pagemap_fd,
>> +			int kpageflags_fd, int orders[], int nr_orders)
>> +{
>> +	int *vaddr_orders;
>> +	int status;
>> +	int i;
>> +
>> +	vaddr_orders = (int *)malloc(sizeof(int) * nr_orders);
>> +
>> +	if (!vaddr_orders)
>> +		ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders");
>> +
>> +	memset(vaddr_orders, 0, sizeof(int) * nr_orders);
>> +	status = gather_folio_orders(vaddr_start, len, pagemap_fd,
>> +				     kpageflags_fd, vaddr_orders, nr_orders);
>> +	if (status)
>> +		goto out;
>> +
>> +	status = 0;
>> +	for (i = 0; i < nr_orders; i++)
>> +		if (vaddr_orders[i] != orders[i]) {
>> +			ksft_print_msg("order %d: expected: %d got %d\n", i,
>> +				       orders[i], vaddr_orders[i]);
>> +			status = -1;
>> +		}
>> +
>> +out:
>> +	free(vaddr_orders);
>> +	return status;
>> +}
>
> -- 
> Wei Yang
> Help you, Help me


Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 3/4] selftests/mm: reimplement is_backed_by_thp() with more precise check
  2025-08-13 21:41   ` Wei Yang
@ 2025-08-13 21:58     ` Zi Yan
  0 siblings, 0 replies; 13+ messages in thread
From: Zi Yan @ 2025-08-13 21:58 UTC (permalink / raw)
  To: Wei Yang
  Cc: wang lian, Baolin Wang, David Hildenbrand, linux-mm,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

On 13 Aug 2025, at 17:41, Wei Yang wrote:

> On Tue, Aug 12, 2025 at 11:55:11AM -0400, Zi Yan wrote:
>> and rename it to is_backed_by_folio().
>>
>> is_backed_by_folio() checks if the given vaddr is backed a folio with
>> a given order. It does so by:
>> 1. getting the pfn of the vaddr;
>> 2. checking kpageflags of the pfn;
>>
>> if order is greater than 0:
>> 3. checking kpageflags of the head pfn;
>> 4. checking kpageflags of all tail pfns.
>>
>> pmd_order is added to split_huge_page_test.c and replaces max_order.
>>
>> Signed-off-by: Zi Yan <ziy@nvidia.com>
>> ---
>> .../selftests/mm/split_huge_page_test.c       | 67 +++++++++++++------
>> tools/testing/selftests/mm/vm_util.c          |  2 +-
>> tools/testing/selftests/mm/vm_util.h          |  1 +
>> 3 files changed, 48 insertions(+), 22 deletions(-)
>>
>> diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
>> index 63ac82f0b9e0..3aaf783f339f 100644
>> --- a/tools/testing/selftests/mm/split_huge_page_test.c
>> +++ b/tools/testing/selftests/mm/split_huge_page_test.c
>> @@ -25,6 +25,7 @@
>> uint64_t pagesize;
>> unsigned int pageshift;
>> uint64_t pmd_pagesize;
>> +unsigned int pmd_order;
>>
>> #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
>> #define SMAP_PATH "/proc/self/smaps"
>> @@ -36,23 +37,48 @@ uint64_t pmd_pagesize;
>>
>> #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
>>
>> -int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
>> +int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd)
>> {
>> -	uint64_t paddr;
>> -	uint64_t page_flags;
>> +	unsigned long pfn_head;
>> +	uint64_t pfn_flags;
>> +	unsigned long pfn;
>> +	unsigned long i;
>>
>> -	if (pagemap_file) {
>> -		pread(pagemap_file, &paddr, sizeof(paddr),
>> -			((long)vaddr >> pageshift) * sizeof(paddr));
>> +	if (!pagemap_fd || !kpageflags_fd)
>> +		return 0;
>
> The same in patch 2.

Will fix it.

>
>>
>> -		if (kpageflags_file) {
>> -			pread(kpageflags_file, &page_flags, sizeof(page_flags),
>> -				PAGEMAP_PFN(paddr) * sizeof(page_flags));
>> +	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
>>
>> -			return !!(page_flags & KPF_THP);
>> -		}
>> +	if (pfn == -1UL)
>> +		return 0;
>> +
>> +	if (get_pfn_flags(pfn, kpageflags_fd, &pfn_flags))
>> +		return 0;
>> +
>> +	if (!order) {
>> +		if (pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))
>> +			return 0;
>> +		return 1;
>> 	}
>> -	return 0;
>> +
>> +	if (!(pfn_flags & KPF_THP))
>> +		return 0;
>> +
>> +	pfn_head = pfn & ~((1 << order) - 1);
>> +
>> +	if (get_pfn_flags(pfn_head, kpageflags_fd, &pfn_flags))
>> +		return 0;
>> +
>> +	if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD)))
>> +		return 0;
>> +
>> +	for (i = 1; i < (1UL << order) - 1; i++) {
>
> Do we miss the last tail?

Yes, will fix it.

>
>> +		if (get_pfn_flags(pfn_head + i, kpageflags_fd, &pfn_flags))
>> +			return 0;
>> +		if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_TAIL)))
>> +			return 0;
>> +	}
>
> If this folio is larger than order, would it still return 1?

Yes, but it should be good enough for current use. Will add a comment about it.

Thanks for the review.

Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 4/4] selftests/mm: check after-split folio orders in split_huge_page_test.
  2025-08-12 15:55 ` [PATCH v3 4/4] selftests/mm: check after-split folio orders in split_huge_page_test Zi Yan
@ 2025-08-14  9:16   ` Wei Yang
  2025-08-14 13:35     ` Zi Yan
  0 siblings, 1 reply; 13+ messages in thread
From: Wei Yang @ 2025-08-14  9:16 UTC (permalink / raw)
  To: Zi Yan, g
  Cc: Wei Yang, wang lian, Baolin Wang, David Hildenbrand, linux-mm,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

On Tue, Aug 12, 2025 at 11:55:12AM -0400, Zi Yan wrote:
>Instead of just checking the existence of PMD folios before and after folio
>split tests, use check_folio_orders() to check after-split folio orders.
>
>The following tests are not changed:
>1. split_pte_mapped_thp: the test already uses kpageflags to check;
>2. split_file_backed_thp: no vaddr available.
>
>Signed-off-by: Zi Yan <ziy@nvidia.com>
>---
> .../selftests/mm/split_huge_page_test.c       | 85 +++++++++++++------
> 1 file changed, 61 insertions(+), 24 deletions(-)
>
>diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
>index 3aaf783f339f..1ea2c7f22962 100644
>--- a/tools/testing/selftests/mm/split_huge_page_test.c
>+++ b/tools/testing/selftests/mm/split_huge_page_test.c
>@@ -26,6 +26,7 @@ uint64_t pagesize;
> unsigned int pageshift;
> uint64_t pmd_pagesize;
> unsigned int pmd_order;
>+int *expected_orders;
> 
> #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
> #define SMAP_PATH "/proc/self/smaps"
>@@ -37,6 +38,11 @@ unsigned int pmd_order;
> 
> #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
> 
>+const char *pagemap_proc = "/proc/self/pagemap";
>+const char *kpageflags_proc = "/proc/kpageflags";
>+int pagemap_fd;
>+int kpageflags_fd;
>+
> int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd)
> {
> 	unsigned long pfn_head;
>@@ -49,18 +55,21 @@ int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd
> 
> 	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
> 
>+	/* non present page */
> 	if (pfn == -1UL)
> 		return 0;
> 
> 	if (get_pfn_flags(pfn, kpageflags_fd, &pfn_flags))
> 		return 0;
> 
>+	/* check for order-0 pages */
> 	if (!order) {
> 		if (pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))
> 			return 0;
> 		return 1;
> 	}
> 
>+	/* non THP folio */
> 	if (!(pfn_flags & KPF_THP))
> 		return 0;
> 
>@@ -69,9 +78,11 @@ int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd
> 	if (get_pfn_flags(pfn_head, kpageflags_fd, &pfn_flags))
> 		return 0;
> 
>+	/* head PFN has no compound_head flag set */
> 	if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD)))
> 		return 0;
> 
>+	/* check all tail PFN flags */
> 	for (i = 1; i < (1UL << order) - 1; i++) {
> 		if (get_pfn_flags(pfn_head + i, kpageflags_fd, &pfn_flags))
> 			return 0;

The comment in is_backed_by_folio() is more proper to be in previous patch?

>@@ -198,6 +209,12 @@ void split_pmd_thp_to_order(int order)
> 		if (one_page[i] != (char)i)
> 			ksft_exit_fail_msg("%ld byte corrupted\n", i);
> 
>+	memset(expected_orders, 0, sizeof(int) * (pmd_order + 1));
>+	expected_orders[order] = 4 << (pmd_order - order);
>+
>+	if (check_folio_orders(one_page, len, pagemap_fd, kpageflags_fd,
>+			       expected_orders, (pmd_order + 1)))
>+		ksft_exit_fail_msg("Unexpected THP split\n");
> 
> 	if (!check_huge_anon(one_page, 0, pmd_pagesize))
> 		ksft_exit_fail_msg("Still AnonHugePages not split\n");
>@@ -212,22 +229,6 @@ void split_pte_mapped_thp(void)
> 	size_t len = 4 * pmd_pagesize;
> 	uint64_t thp_size;
> 	size_t i;
>-	const char *pagemap_template = "/proc/%d/pagemap";
>-	const char *kpageflags_proc = "/proc/kpageflags";
>-	char pagemap_proc[255];
>-	int pagemap_fd;
>-	int kpageflags_fd;
>-
>-	if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0)
>-		ksft_exit_fail_msg("get pagemap proc error: %s\n", strerror(errno));
>-
>-	pagemap_fd = open(pagemap_proc, O_RDONLY);
>-	if (pagemap_fd == -1)
>-		ksft_exit_fail_msg("read pagemap: %s\n", strerror(errno));
>-
>-	kpageflags_fd = open(kpageflags_proc, O_RDONLY);
>-	if (kpageflags_fd == -1)
>-		ksft_exit_fail_msg("read kpageflags: %s\n", strerror(errno));
> 
> 	one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE,
> 			MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
>@@ -285,8 +286,6 @@ void split_pte_mapped_thp(void)
> 
> 	ksft_test_result_pass("Split PTE-mapped huge pages successful\n");
> 	munmap(one_page, len);
>-	close(pagemap_fd);
>-	close(kpageflags_fd);
> }
> 
> void split_file_backed_thp(int order)
>@@ -489,6 +488,7 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
> 		int order, int offset)
> {
> 	int fd;
>+	char *split_addr;
> 	char *addr;
> 	size_t i;
> 	char testfile[INPUT_MAX];
>@@ -502,14 +502,27 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
> 	err = create_pagecache_thp_and_fd(testfile, fd_size, &fd, &addr);
> 	if (err)
> 		return;
>+
> 	err = 0;
> 
>-	if (offset == -1)
>-		write_debugfs(PID_FMT, getpid(), (uint64_t)addr,
>-			      (uint64_t)addr + fd_size, order);
>-	else
>-		write_debugfs(PID_FMT_OFFSET, getpid(), (uint64_t)addr,
>-			      (uint64_t)addr + fd_size, order, offset);
>+	memset(expected_orders, 0, sizeof(int) * (pmd_order + 1));

I am not familiar with split, you change it to split on each pmd_pagesize from
4 pmd_pagesize. Is there any difference?

>+	if (offset == -1) {
>+		for (split_addr = addr; split_addr < addr + fd_size; split_addr += pmd_pagesize)
>+			write_debugfs(PID_FMT, getpid(), (uint64_t)split_addr,
>+				      (uint64_t)split_addr + pagesize, order);

                                                ^--- here should be vaddr_end

Curious why not (uint64_t)split_addr + pmd_pagesize?

>+
>+		expected_orders[order] = fd_size / (pagesize << order);
>+	} else {
>+		int times = fd_size / pmd_pagesize;
>+
>+		for (split_addr = addr; split_addr < addr + fd_size; split_addr += pmd_pagesize)
>+			write_debugfs(PID_FMT_OFFSET, getpid(), (uint64_t)split_addr,
>+				      (uint64_t)split_addr + pagesize, order, offset);

As above.

>+
>+		for (i = order + 1; i < pmd_order; i++)
>+			expected_orders[i] = times;
>+		expected_orders[order] = 2 * times;
>+	}
> 
> 	for (i = 0; i < fd_size; i++)
> 		if (*(addr + i) != (char)i) {
>@@ -518,6 +531,13 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
> 			goto out;
> 		}
> 
>+	if (check_folio_orders(addr, fd_size, pagemap_fd, kpageflags_fd,
>+			       expected_orders, (pmd_order + 1))) {
>+		ksft_print_msg("Unexpected THP split\n");
>+		err = 1;
>+		goto out;
>+	}
>+
> 	if (!check_huge_file(addr, 0, pmd_pagesize)) {
> 		ksft_print_msg("Still FilePmdMapped not split\n");
> 		err = EXIT_FAILURE;
>@@ -569,9 +589,22 @@ int main(int argc, char **argv)
> 
> 	nr_pages = pmd_pagesize / pagesize;
> 	pmd_order = GET_ORDER(nr_pages);
>+
>+	expected_orders = (int *)malloc(sizeof(int) * (pmd_order + 1));
>+	if (!expected_orders)
>+		ksft_exit_fail_msg("Fail to allocate memory: %s\n", strerror(errno));
>+
> 	tests = 2 + (pmd_order - 1) + (2 * pmd_order) + (pmd_order - 1) * 4 + 2;
> 	ksft_set_plan(tests);
> 
>+	pagemap_fd = open(pagemap_proc, O_RDONLY);
>+	if (pagemap_fd == -1)
>+		ksft_exit_fail_msg("read pagemap: %s\n", strerror(errno));
>+
>+	kpageflags_fd = open(kpageflags_proc, O_RDONLY);
>+	if (kpageflags_fd == -1)
>+		ksft_exit_fail_msg("read kpageflags: %s\n", strerror(errno));
>+
> 	fd_size = 2 * pmd_pagesize;
> 
> 	split_pmd_zero_pages();
>@@ -596,6 +629,10 @@ int main(int argc, char **argv)
> 			split_thp_in_pagecache_to_order_at(fd_size, fs_loc, i, offset);
> 	cleanup_thp_fs(fs_loc, created_tmp);
> 
>+	close(pagemap_fd);
>+	close(kpageflags_fd);
>+	free(expected_orders);
>+
> 	ksft_finished();
> 
> 	return 0;
>-- 
>2.47.2

-- 
Wei Yang
Help you, Help me

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 4/4] selftests/mm: check after-split folio orders in split_huge_page_test.
  2025-08-14  9:16   ` Wei Yang
@ 2025-08-14 13:35     ` Zi Yan
  0 siblings, 0 replies; 13+ messages in thread
From: Zi Yan @ 2025-08-14 13:35 UTC (permalink / raw)
  To: Wei Yang
  Cc: wang lian, Baolin Wang, David Hildenbrand, linux-mm,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
	linux-kernel, linux-kselftest

On 14 Aug 2025, at 5:16, Wei Yang wrote:

> On Tue, Aug 12, 2025 at 11:55:12AM -0400, Zi Yan wrote:
>> Instead of just checking the existence of PMD folios before and after folio
>> split tests, use check_folio_orders() to check after-split folio orders.
>>
>> The following tests are not changed:
>> 1. split_pte_mapped_thp: the test already uses kpageflags to check;
>> 2. split_file_backed_thp: no vaddr available.
>>
>> Signed-off-by: Zi Yan <ziy@nvidia.com>
>> ---
>> .../selftests/mm/split_huge_page_test.c       | 85 +++++++++++++------
>> 1 file changed, 61 insertions(+), 24 deletions(-)
>>
>> diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
>> index 3aaf783f339f..1ea2c7f22962 100644
>> --- a/tools/testing/selftests/mm/split_huge_page_test.c
>> +++ b/tools/testing/selftests/mm/split_huge_page_test.c
>> @@ -26,6 +26,7 @@ uint64_t pagesize;
>> unsigned int pageshift;
>> uint64_t pmd_pagesize;
>> unsigned int pmd_order;
>> +int *expected_orders;
>>
>> #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
>> #define SMAP_PATH "/proc/self/smaps"
>> @@ -37,6 +38,11 @@ unsigned int pmd_order;
>>
>> #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
>>
>> +const char *pagemap_proc = "/proc/self/pagemap";
>> +const char *kpageflags_proc = "/proc/kpageflags";
>> +int pagemap_fd;
>> +int kpageflags_fd;
>> +
>> int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd)
>> {
>> 	unsigned long pfn_head;
>> @@ -49,18 +55,21 @@ int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd
>>
>> 	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
>>
>> +	/* non present page */
>> 	if (pfn == -1UL)
>> 		return 0;
>>
>> 	if (get_pfn_flags(pfn, kpageflags_fd, &pfn_flags))
>> 		return 0;
>>
>> +	/* check for order-0 pages */
>> 	if (!order) {
>> 		if (pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))
>> 			return 0;
>> 		return 1;
>> 	}
>>
>> +	/* non THP folio */
>> 	if (!(pfn_flags & KPF_THP))
>> 		return 0;
>>
>> @@ -69,9 +78,11 @@ int is_backed_by_folio(char *vaddr, int order, int pagemap_fd, int kpageflags_fd
>> 	if (get_pfn_flags(pfn_head, kpageflags_fd, &pfn_flags))
>> 		return 0;
>>
>> +	/* head PFN has no compound_head flag set */
>> 	if (!(pfn_flags & (KPF_THP | KPF_COMPOUND_HEAD)))
>> 		return 0;
>>
>> +	/* check all tail PFN flags */
>> 	for (i = 1; i < (1UL << order) - 1; i++) {
>> 		if (get_pfn_flags(pfn_head + i, kpageflags_fd, &pfn_flags))
>> 			return 0;
>
> The comment in is_backed_by_folio() is more proper to be in previous patch?

Oops, these should be in the prior patch.

>
>> @@ -198,6 +209,12 @@ void split_pmd_thp_to_order(int order)
>> 		if (one_page[i] != (char)i)
>> 			ksft_exit_fail_msg("%ld byte corrupted\n", i);
>>
>> +	memset(expected_orders, 0, sizeof(int) * (pmd_order + 1));
>> +	expected_orders[order] = 4 << (pmd_order - order);
>> +
>> +	if (check_folio_orders(one_page, len, pagemap_fd, kpageflags_fd,
>> +			       expected_orders, (pmd_order + 1)))
>> +		ksft_exit_fail_msg("Unexpected THP split\n");
>>
>> 	if (!check_huge_anon(one_page, 0, pmd_pagesize))
>> 		ksft_exit_fail_msg("Still AnonHugePages not split\n");
>> @@ -212,22 +229,6 @@ void split_pte_mapped_thp(void)
>> 	size_t len = 4 * pmd_pagesize;
>> 	uint64_t thp_size;
>> 	size_t i;
>> -	const char *pagemap_template = "/proc/%d/pagemap";
>> -	const char *kpageflags_proc = "/proc/kpageflags";
>> -	char pagemap_proc[255];
>> -	int pagemap_fd;
>> -	int kpageflags_fd;
>> -
>> -	if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0)
>> -		ksft_exit_fail_msg("get pagemap proc error: %s\n", strerror(errno));
>> -
>> -	pagemap_fd = open(pagemap_proc, O_RDONLY);
>> -	if (pagemap_fd == -1)
>> -		ksft_exit_fail_msg("read pagemap: %s\n", strerror(errno));
>> -
>> -	kpageflags_fd = open(kpageflags_proc, O_RDONLY);
>> -	if (kpageflags_fd == -1)
>> -		ksft_exit_fail_msg("read kpageflags: %s\n", strerror(errno));
>>
>> 	one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE,
>> 			MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
>> @@ -285,8 +286,6 @@ void split_pte_mapped_thp(void)
>>
>> 	ksft_test_result_pass("Split PTE-mapped huge pages successful\n");
>> 	munmap(one_page, len);
>> -	close(pagemap_fd);
>> -	close(kpageflags_fd);
>> }
>>
>> void split_file_backed_thp(int order)
>> @@ -489,6 +488,7 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
>> 		int order, int offset)
>> {
>> 	int fd;
>> +	char *split_addr;
>> 	char *addr;
>> 	size_t i;
>> 	char testfile[INPUT_MAX];
>> @@ -502,14 +502,27 @@ void split_thp_in_pagecache_to_order_at(size_t fd_size, const char *fs_loc,
>> 	err = create_pagecache_thp_and_fd(testfile, fd_size, &fd, &addr);
>> 	if (err)
>> 		return;
>> +
>> 	err = 0;
>>
>> -	if (offset == -1)
>> -		write_debugfs(PID_FMT, getpid(), (uint64_t)addr,
>> -			      (uint64_t)addr + fd_size, order);
>> -	else
>> -		write_debugfs(PID_FMT_OFFSET, getpid(), (uint64_t)addr,
>> -			      (uint64_t)addr + fd_size, order, offset);
>> +	memset(expected_orders, 0, sizeof(int) * (pmd_order + 1));
>
> I am not familiar with split, you change it to split on each pmd_pagesize from
> 4 pmd_pagesize. Is there any difference?
>
>> +	if (offset == -1) {
>> +		for (split_addr = addr; split_addr < addr + fd_size; split_addr += pmd_pagesize)
>> +			write_debugfs(PID_FMT, getpid(), (uint64_t)split_addr,
>> +				      (uint64_t)split_addr + pagesize, order);
>
>                                                 ^--- here should be vaddr_end
>
> Curious why not (uint64_t)split_addr + pmd_pagesize?

It is in V1->V2 changelog. split_huge_pages_pid() always step in PAGESIZE
to be able to split mremapped PTE-mapped THPs. Using [addr, addr + fd_size)
makes the PMD THP be split multiple times. The goal of this test is to
check the result of one non-uniform split,
so use [split_addr, split_addr + pagesize) to achieve that.

I will add the above to commit message and a comment here.

>
>> +
>> +		expected_orders[order] = fd_size / (pagesize << order);
>> +	} else {
>> +		int times = fd_size / pmd_pagesize;
>> +
>> +		for (split_addr = addr; split_addr < addr + fd_size; split_addr += pmd_pagesize)
>> +			write_debugfs(PID_FMT_OFFSET, getpid(), (uint64_t)split_addr,
>> +				      (uint64_t)split_addr + pagesize, order, offset);
>
> As above.
>

See above.

--
Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper.
  2025-08-13  3:38   ` wang lian
@ 2025-08-14 17:50     ` Zi Yan
  0 siblings, 0 replies; 13+ messages in thread
From: Zi Yan @ 2025-08-14 17:50 UTC (permalink / raw)
  To: wang lian
  Cc: Wei Yang, Baolin Wang, David Hildenbrand, linux-mm, Andrew Morton,
	Lorenzo Stoakes, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, linux-kernel,
	linux-kselftest

On 12 Aug 2025, at 23:38, wang lian wrote:

>> On Aug 12, 2025, at 23:55, Zi Yan <ziy@nvidia.com> wrote:
>>
>> The helper gathers an folio order statistics of folios within a virtual
>> address range and checks it against a given order list. It aims to provide
>> a more precise folio order check instead of just checking the existence of
>> PMD folios.
>>
>> Signed-off-by: Zi Yan <ziy@nvidia.com>
>> ---
>> .../selftests/mm/split_huge_page_test.c       |   4 +-
>> tools/testing/selftests/mm/vm_util.c          | 173 ++++++++++++++++++
>> tools/testing/selftests/mm/vm_util.h          |   7 +
>> 3 files changed, 181 insertions(+), 3 deletions(-)
>>
>> diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
>> index 5d07b0b89226..63ac82f0b9e0 100644
>> --- a/tools/testing/selftests/mm/split_huge_page_test.c
>> +++ b/tools/testing/selftests/mm/split_huge_page_test.c
>> @@ -34,8 +34,6 @@ uint64_t pmd_pagesize;
>> #define PID_FMT_OFFSET "%d,0x%lx,0x%lx,%d,%d"
>> #define PATH_FMT "%s,0x%lx,0x%lx,%d"
>>
>> -#define PFN_MASK     ((1UL<<55)-1)
>> -#define KPF_THP      (1UL<<22)
>> #define GET_ORDER(nr_pages)    (31 - __builtin_clz(nr_pages))
>>
>> int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
>> @@ -49,7 +47,7 @@ int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
>>
>> 		if (kpageflags_file) {
>> 			pread(kpageflags_file, &page_flags, sizeof(page_flags),
>> -				(paddr & PFN_MASK) * sizeof(page_flags));
>> +				PAGEMAP_PFN(paddr) * sizeof(page_flags));
>>
>> 			return !!(page_flags & KPF_THP);
>> 		}
>> diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
>> index 6a239aa413e2..4d952d1bc96d 100644
>> --- a/tools/testing/selftests/mm/vm_util.c
>> +++ b/tools/testing/selftests/mm/vm_util.c
>> @@ -338,6 +338,179 @@ int detect_hugetlb_page_sizes(size_t sizes[], int max)
>> 	return count;
>> }
>>
>> +static int get_pfn_flags(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
>> +{
>> +	size_t count;
>> +
>> +	count = pread(kpageflags_fd, flags, sizeof(*flags),
>> +		      pfn * sizeof(*flags));
>> +
>> +	if (count != sizeof(*flags))
>> +		return -1;
>> +
>> +	return 0;
>> +}
>> +
>> +static int get_page_flags(char *vaddr, int pagemap_fd, int kpageflags_fd,
>> +			  uint64_t *flags)
>> +{
>> +	unsigned long pfn;
>> +
>> +	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
>> +	/*
>> +	 * Treat non-present page as a page without any flag, so that
>> +	 * gather_folio_orders() just record the current folio order.
>> +	 */
>> +	if (pfn == -1UL) {
>> +		*flags = 0;
>> +		return 1;
>> +	}
>> +
>> +	if (get_pfn_flags(pfn, kpageflags_fd, flags))
>> +		return -1;
>> +
>> +	return 0;
>> +}
>> +
>> +/*
>> + * gather_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_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.
>> + *
>> + *
>> + * Return: 0 - no error, -1 - unhandled cases
>> + */
>> +static int gather_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 || !kpageflags_fd)
>> +		return -1;
>> +	if (nr_orders <= 0)
>> +		return -1;
>> +
>> +	for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
>> +		char *next_folio_vaddr;
>> +		int status;
>> +
>> +		status = get_page_flags(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 {
>> +			/* not a head nor a tail in a THP? */
>> +			if (!(page_flags & KPF_COMPOUND_TAIL))
>> +				return -1;
>> +
>> +			vaddr += psize();
>> +			continue;
>> +		}
>> +
>> +		next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
>> +
>> +		if (next_folio_vaddr >= vaddr_start + len)
>> +			break;
>> +
>> +		while ((status = get_page_flags(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;
>> +			}
>> +
>> +			/* not a head nor a tail in a THP? */
>> +			if (!(page_flags & KPF_COMPOUND_TAIL))
>> +				return -1;
>> +
>> +			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;
>> +}
>> +
>> +int check_folio_orders(char *vaddr_start, size_t len, int pagemap_fd,
>> +			int kpageflags_fd, int orders[], int nr_orders)
>> +{
>> +	int *vaddr_orders;
>> +	int status;
>> +	int i;
>> +
>> +	vaddr_orders = (int *)malloc(sizeof(int) * nr_orders);
>> +
>> +	if (!vaddr_orders)
>> +		ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders");
>> +
>> +	memset(vaddr_orders, 0, sizeof(int) * nr_orders);
>> +	status = gather_folio_orders(vaddr_start, len, pagemap_fd,
>> +				     kpageflags_fd, vaddr_orders, nr_orders);
>> +	if (status)
>> +		goto out;
>> +
>> +	status = 0;
>
> Nit.
> It seems redundant.
> Would you consider removing it for a bit more conciseness?
> This doesn't block my approval, of course.
> Reviewed-by: wang lian <lianux.mm@gmail.com>

Sure. Thanks.

Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2025-08-14 17:50 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-12 15:55 [PATCH v3 0/4] Better split_huge_page_test result check Zi Yan
2025-08-12 15:55 ` [PATCH v3 1/4] mm/huge_memory: add new_order and offset to split_huge_pages*() pr_debug Zi Yan
2025-08-12 15:55 ` [PATCH v3 2/4] selftests/mm: add check_folio_orders() helper Zi Yan
2025-08-13  3:38   ` wang lian
2025-08-14 17:50     ` Zi Yan
2025-08-13 21:12   ` Wei Yang
2025-08-13 21:56     ` Zi Yan
2025-08-12 15:55 ` [PATCH v3 3/4] selftests/mm: reimplement is_backed_by_thp() with more precise check Zi Yan
2025-08-13 21:41   ` Wei Yang
2025-08-13 21:58     ` Zi Yan
2025-08-12 15:55 ` [PATCH v3 4/4] selftests/mm: check after-split folio orders in split_huge_page_test Zi Yan
2025-08-14  9:16   ` Wei Yang
2025-08-14 13:35     ` Zi Yan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).