1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * A test of splitting PMD THPs and PTE-mapped THPs from a specified virtual
4  * address range in a process via <debugfs>/split_huge_pages interface.
5  */
6 
7 #define _GNU_SOURCE
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <stdarg.h>
11 #include <unistd.h>
12 #include <inttypes.h>
13 #include <string.h>
14 #include <fcntl.h>
15 #include <sys/mman.h>
16 #include <sys/mount.h>
17 #include <malloc.h>
18 #include <stdbool.h>
19 
20 uint64_t pagesize;
21 unsigned int pageshift;
22 uint64_t pmd_pagesize;
23 
24 #define PMD_SIZE_PATH "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size"
25 #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
26 #define SMAP_PATH "/proc/self/smaps"
27 #define INPUT_MAX 80
28 
29 #define PID_FMT "%d,0x%lx,0x%lx"
30 #define PATH_FMT "%s,0x%lx,0x%lx"
31 
32 #define PFN_MASK     ((1UL<<55)-1)
33 #define KPF_THP      (1UL<<22)
34 
is_backed_by_thp(char * vaddr,int pagemap_file,int kpageflags_file)35 int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
36 {
37 	uint64_t paddr;
38 	uint64_t page_flags;
39 
40 	if (pagemap_file) {
41 		pread(pagemap_file, &paddr, sizeof(paddr),
42 			((long)vaddr >> pageshift) * sizeof(paddr));
43 
44 		if (kpageflags_file) {
45 			pread(kpageflags_file, &page_flags, sizeof(page_flags),
46 				(paddr & PFN_MASK) * sizeof(page_flags));
47 
48 			return !!(page_flags & KPF_THP);
49 		}
50 	}
51 	return 0;
52 }
53 
54 
read_pmd_pagesize(void)55 static uint64_t read_pmd_pagesize(void)
56 {
57 	int fd;
58 	char buf[20];
59 	ssize_t num_read;
60 
61 	fd = open(PMD_SIZE_PATH, O_RDONLY);
62 	if (fd == -1) {
63 		perror("Open hpage_pmd_size failed");
64 		exit(EXIT_FAILURE);
65 	}
66 	num_read = read(fd, buf, 19);
67 	if (num_read < 1) {
68 		close(fd);
69 		perror("Read hpage_pmd_size failed");
70 		exit(EXIT_FAILURE);
71 	}
72 	buf[num_read] = '\0';
73 	close(fd);
74 
75 	return strtoul(buf, NULL, 10);
76 }
77 
write_file(const char * path,const char * buf,size_t buflen)78 static int write_file(const char *path, const char *buf, size_t buflen)
79 {
80 	int fd;
81 	ssize_t numwritten;
82 
83 	fd = open(path, O_WRONLY);
84 	if (fd == -1)
85 		return 0;
86 
87 	numwritten = write(fd, buf, buflen - 1);
88 	close(fd);
89 	if (numwritten < 1)
90 		return 0;
91 
92 	return (unsigned int) numwritten;
93 }
94 
write_debugfs(const char * fmt,...)95 static void write_debugfs(const char *fmt, ...)
96 {
97 	char input[INPUT_MAX];
98 	int ret;
99 	va_list argp;
100 
101 	va_start(argp, fmt);
102 	ret = vsnprintf(input, INPUT_MAX, fmt, argp);
103 	va_end(argp);
104 
105 	if (ret >= INPUT_MAX) {
106 		printf("%s: Debugfs input is too long\n", __func__);
107 		exit(EXIT_FAILURE);
108 	}
109 
110 	if (!write_file(SPLIT_DEBUGFS, input, ret + 1)) {
111 		perror(SPLIT_DEBUGFS);
112 		exit(EXIT_FAILURE);
113 	}
114 }
115 
116 #define MAX_LINE_LENGTH 500
117 
check_for_pattern(FILE * fp,const char * pattern,char * buf)118 static bool check_for_pattern(FILE *fp, const char *pattern, char *buf)
119 {
120 	while (fgets(buf, MAX_LINE_LENGTH, fp) != NULL) {
121 		if (!strncmp(buf, pattern, strlen(pattern)))
122 			return true;
123 	}
124 	return false;
125 }
126 
check_huge(void * addr)127 static uint64_t check_huge(void *addr)
128 {
129 	uint64_t thp = 0;
130 	int ret;
131 	FILE *fp;
132 	char buffer[MAX_LINE_LENGTH];
133 	char addr_pattern[MAX_LINE_LENGTH];
134 
135 	ret = snprintf(addr_pattern, MAX_LINE_LENGTH, "%08lx-",
136 		       (unsigned long) addr);
137 	if (ret >= MAX_LINE_LENGTH) {
138 		printf("%s: Pattern is too long\n", __func__);
139 		exit(EXIT_FAILURE);
140 	}
141 
142 
143 	fp = fopen(SMAP_PATH, "r");
144 	if (!fp) {
145 		printf("%s: Failed to open file %s\n", __func__, SMAP_PATH);
146 		exit(EXIT_FAILURE);
147 	}
148 	if (!check_for_pattern(fp, addr_pattern, buffer))
149 		goto err_out;
150 
151 	/*
152 	 * Fetch the AnonHugePages: in the same block and check the number of
153 	 * hugepages.
154 	 */
155 	if (!check_for_pattern(fp, "AnonHugePages:", buffer))
156 		goto err_out;
157 
158 	if (sscanf(buffer, "AnonHugePages:%10ld kB", &thp) != 1) {
159 		printf("Reading smap error\n");
160 		exit(EXIT_FAILURE);
161 	}
162 
163 err_out:
164 	fclose(fp);
165 	return thp;
166 }
167 
split_pmd_thp(void)168 void split_pmd_thp(void)
169 {
170 	char *one_page;
171 	size_t len = 4 * pmd_pagesize;
172 	uint64_t thp_size;
173 	size_t i;
174 
175 	one_page = memalign(pmd_pagesize, len);
176 
177 	if (!one_page) {
178 		printf("Fail to allocate memory\n");
179 		exit(EXIT_FAILURE);
180 	}
181 
182 	madvise(one_page, len, MADV_HUGEPAGE);
183 
184 	for (i = 0; i < len; i++)
185 		one_page[i] = (char)i;
186 
187 	thp_size = check_huge(one_page);
188 	if (!thp_size) {
189 		printf("No THP is allocated\n");
190 		exit(EXIT_FAILURE);
191 	}
192 
193 	/* split all THPs */
194 	write_debugfs(PID_FMT, getpid(), (uint64_t)one_page,
195 		(uint64_t)one_page + len);
196 
197 	for (i = 0; i < len; i++)
198 		if (one_page[i] != (char)i) {
199 			printf("%ld byte corrupted\n", i);
200 			exit(EXIT_FAILURE);
201 		}
202 
203 
204 	thp_size = check_huge(one_page);
205 	if (thp_size) {
206 		printf("Still %ld kB AnonHugePages not split\n", thp_size);
207 		exit(EXIT_FAILURE);
208 	}
209 
210 	printf("Split huge pages successful\n");
211 	free(one_page);
212 }
213 
split_pte_mapped_thp(void)214 void split_pte_mapped_thp(void)
215 {
216 	char *one_page, *pte_mapped, *pte_mapped2;
217 	size_t len = 4 * pmd_pagesize;
218 	uint64_t thp_size;
219 	size_t i;
220 	const char *pagemap_template = "/proc/%d/pagemap";
221 	const char *kpageflags_proc = "/proc/kpageflags";
222 	char pagemap_proc[255];
223 	int pagemap_fd;
224 	int kpageflags_fd;
225 
226 	if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0) {
227 		perror("get pagemap proc error");
228 		exit(EXIT_FAILURE);
229 	}
230 	pagemap_fd = open(pagemap_proc, O_RDONLY);
231 
232 	if (pagemap_fd == -1) {
233 		perror("read pagemap:");
234 		exit(EXIT_FAILURE);
235 	}
236 
237 	kpageflags_fd = open(kpageflags_proc, O_RDONLY);
238 
239 	if (kpageflags_fd == -1) {
240 		perror("read kpageflags:");
241 		exit(EXIT_FAILURE);
242 	}
243 
244 	one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE,
245 			MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
246 
247 	madvise(one_page, len, MADV_HUGEPAGE);
248 
249 	for (i = 0; i < len; i++)
250 		one_page[i] = (char)i;
251 
252 	thp_size = check_huge(one_page);
253 	if (!thp_size) {
254 		printf("No THP is allocated\n");
255 		exit(EXIT_FAILURE);
256 	}
257 
258 	/* remap the first pagesize of first THP */
259 	pte_mapped = mremap(one_page, pagesize, pagesize, MREMAP_MAYMOVE);
260 
261 	/* remap the Nth pagesize of Nth THP */
262 	for (i = 1; i < 4; i++) {
263 		pte_mapped2 = mremap(one_page + pmd_pagesize * i + pagesize * i,
264 				     pagesize, pagesize,
265 				     MREMAP_MAYMOVE|MREMAP_FIXED,
266 				     pte_mapped + pagesize * i);
267 		if (pte_mapped2 == (char *)-1) {
268 			perror("mremap failed");
269 			exit(EXIT_FAILURE);
270 		}
271 	}
272 
273 	/* smap does not show THPs after mremap, use kpageflags instead */
274 	thp_size = 0;
275 	for (i = 0; i < pagesize * 4; i++)
276 		if (i % pagesize == 0 &&
277 		    is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
278 			thp_size++;
279 
280 	if (thp_size != 4) {
281 		printf("Some THPs are missing during mremap\n");
282 		exit(EXIT_FAILURE);
283 	}
284 
285 	/* split all remapped THPs */
286 	write_debugfs(PID_FMT, getpid(), (uint64_t)pte_mapped,
287 		      (uint64_t)pte_mapped + pagesize * 4);
288 
289 	/* smap does not show THPs after mremap, use kpageflags instead */
290 	thp_size = 0;
291 	for (i = 0; i < pagesize * 4; i++) {
292 		if (pte_mapped[i] != (char)i) {
293 			printf("%ld byte corrupted\n", i);
294 			exit(EXIT_FAILURE);
295 		}
296 		if (i % pagesize == 0 &&
297 		    is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
298 			thp_size++;
299 	}
300 
301 	if (thp_size) {
302 		printf("Still %ld THPs not split\n", thp_size);
303 		exit(EXIT_FAILURE);
304 	}
305 
306 	printf("Split PTE-mapped huge pages successful\n");
307 	munmap(one_page, len);
308 	close(pagemap_fd);
309 	close(kpageflags_fd);
310 }
311 
split_file_backed_thp(void)312 void split_file_backed_thp(void)
313 {
314 	int status;
315 	int fd;
316 	ssize_t num_written;
317 	char tmpfs_template[] = "/tmp/thp_split_XXXXXX";
318 	const char *tmpfs_loc = mkdtemp(tmpfs_template);
319 	char testfile[INPUT_MAX];
320 	uint64_t pgoff_start = 0, pgoff_end = 1024;
321 
322 	printf("Please enable pr_debug in split_huge_pages_in_file() if you need more info.\n");
323 
324 	status = mount("tmpfs", tmpfs_loc, "tmpfs", 0, "huge=always,size=4m");
325 
326 	if (status) {
327 		printf("Unable to create a tmpfs for testing\n");
328 		exit(EXIT_FAILURE);
329 	}
330 
331 	status = snprintf(testfile, INPUT_MAX, "%s/thp_file", tmpfs_loc);
332 	if (status >= INPUT_MAX) {
333 		printf("Fail to create file-backed THP split testing file\n");
334 		goto cleanup;
335 	}
336 
337 	fd = open(testfile, O_CREAT|O_WRONLY);
338 	if (fd == -1) {
339 		perror("Cannot open testing file\n");
340 		goto cleanup;
341 	}
342 
343 	/* write something to the file, so a file-backed THP can be allocated */
344 	num_written = write(fd, tmpfs_loc, sizeof(tmpfs_loc));
345 	close(fd);
346 
347 	if (num_written < 1) {
348 		printf("Fail to write data to testing file\n");
349 		goto cleanup;
350 	}
351 
352 	/* split the file-backed THP */
353 	write_debugfs(PATH_FMT, testfile, pgoff_start, pgoff_end);
354 
355 	status = unlink(testfile);
356 	if (status)
357 		perror("Cannot remove testing file\n");
358 
359 cleanup:
360 	status = umount(tmpfs_loc);
361 	if (status) {
362 		printf("Unable to umount %s\n", tmpfs_loc);
363 		exit(EXIT_FAILURE);
364 	}
365 	status = rmdir(tmpfs_loc);
366 	if (status) {
367 		perror("cannot remove tmp dir");
368 		exit(EXIT_FAILURE);
369 	}
370 
371 	printf("file-backed THP split test done, please check dmesg for more information\n");
372 }
373 
main(int argc,char ** argv)374 int main(int argc, char **argv)
375 {
376 	if (geteuid() != 0) {
377 		printf("Please run the benchmark as root\n");
378 		exit(EXIT_FAILURE);
379 	}
380 
381 	pagesize = getpagesize();
382 	pageshift = ffs(pagesize) - 1;
383 	pmd_pagesize = read_pmd_pagesize();
384 
385 	split_pmd_thp();
386 	split_pte_mapped_thp();
387 	split_file_backed_thp();
388 
389 	return 0;
390 }
391