xref: /linux/tools/testing/selftests/mm/thuge-gen.c (revision e7d759f31ca295d589f7420719c311870bb3166f)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Test selecting other page sizes for mmap/shmget.
3 
4    Before running this huge pages for each huge page size must have been
5    reserved.
6    For large pages beyond MAX_PAGE_ORDER (like 1GB on x86) boot options must
7    be used.
8    Also shmmax must be increased.
9    And you need to run as root to work around some weird permissions in shm.
10    And nothing using huge pages should run in parallel.
11    When the program aborts you may need to clean up the shm segments with
12    ipcrm -m by hand, like this
13    sudo ipcs | awk '$1 == "0x00000000" {print $2}' | xargs -n1 sudo ipcrm -m
14    (warning this will remove all if someone else uses them) */
15 
16 #define _GNU_SOURCE 1
17 #include <sys/mman.h>
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <sys/ipc.h>
21 #include <sys/shm.h>
22 #include <sys/stat.h>
23 #include <glob.h>
24 #include <assert.h>
25 #include <unistd.h>
26 #include <stdarg.h>
27 #include <string.h>
28 #include "vm_util.h"
29 
30 #define err(x) perror(x), exit(1)
31 
32 #define MAP_HUGE_2MB    (21 << MAP_HUGE_SHIFT)
33 #define MAP_HUGE_1GB    (30 << MAP_HUGE_SHIFT)
34 #define MAP_HUGE_SHIFT  26
35 #define MAP_HUGE_MASK   0x3f
36 #if !defined(MAP_HUGETLB)
37 #define MAP_HUGETLB	0x40000
38 #endif
39 
40 #define SHM_HUGETLB     04000   /* segment will use huge TLB pages */
41 #define SHM_HUGE_SHIFT  26
42 #define SHM_HUGE_MASK   0x3f
43 #define SHM_HUGE_2MB    (21 << SHM_HUGE_SHIFT)
44 #define SHM_HUGE_1GB    (30 << SHM_HUGE_SHIFT)
45 
46 #define NUM_PAGESIZES   5
47 
48 #define NUM_PAGES 4
49 
50 #define Dprintf(fmt...) // printf(fmt)
51 
52 unsigned long page_sizes[NUM_PAGESIZES];
53 int num_page_sizes;
54 
55 int ilog2(unsigned long v)
56 {
57 	int l = 0;
58 	while ((1UL << l) < v)
59 		l++;
60 	return l;
61 }
62 
63 void find_pagesizes(void)
64 {
65 	glob_t g;
66 	int i;
67 	glob("/sys/kernel/mm/hugepages/hugepages-*kB", 0, NULL, &g);
68 	assert(g.gl_pathc <= NUM_PAGESIZES);
69 	for (i = 0; i < g.gl_pathc; i++) {
70 		sscanf(g.gl_pathv[i], "/sys/kernel/mm/hugepages/hugepages-%lukB",
71 				&page_sizes[i]);
72 		page_sizes[i] <<= 10;
73 		printf("Found %luMB\n", page_sizes[i] >> 20);
74 	}
75 	num_page_sizes = g.gl_pathc;
76 	globfree(&g);
77 }
78 
79 void show(unsigned long ps)
80 {
81 	char buf[100];
82 	if (ps == getpagesize())
83 		return;
84 	printf("%luMB: ", ps >> 20);
85 	fflush(stdout);
86 	snprintf(buf, sizeof buf,
87 		"cat /sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
88 		ps >> 10);
89 	system(buf);
90 }
91 
92 unsigned long read_sysfs(int warn, char *fmt, ...)
93 {
94 	char *line = NULL;
95 	size_t linelen = 0;
96 	char buf[100];
97 	FILE *f;
98 	va_list ap;
99 	unsigned long val = 0;
100 
101 	va_start(ap, fmt);
102 	vsnprintf(buf, sizeof buf, fmt, ap);
103 	va_end(ap);
104 
105 	f = fopen(buf, "r");
106 	if (!f) {
107 		if (warn)
108 			printf("missing %s\n", buf);
109 		return 0;
110 	}
111 	if (getline(&line, &linelen, f) > 0) {
112 		sscanf(line, "%lu", &val);
113 	}
114 	fclose(f);
115 	free(line);
116 	return val;
117 }
118 
119 unsigned long read_free(unsigned long ps)
120 {
121 	return read_sysfs(ps != getpagesize(),
122 			"/sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
123 			ps >> 10);
124 }
125 
126 void test_mmap(unsigned long size, unsigned flags)
127 {
128 	char *map;
129 	unsigned long before, after;
130 	int err;
131 
132 	before = read_free(size);
133 	map = mmap(NULL, size*NUM_PAGES, PROT_READ|PROT_WRITE,
134 			MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|flags, -1, 0);
135 
136 	if (map == (char *)-1) err("mmap");
137 	memset(map, 0xff, size*NUM_PAGES);
138 	after = read_free(size);
139 	Dprintf("before %lu after %lu diff %ld size %lu\n",
140 		before, after, before - after, size);
141 	assert(size == getpagesize() || (before - after) == NUM_PAGES);
142 	show(size);
143 	err = munmap(map, size * NUM_PAGES);
144 	assert(!err);
145 }
146 
147 void test_shmget(unsigned long size, unsigned flags)
148 {
149 	int id;
150 	unsigned long before, after;
151 	int err;
152 
153 	before = read_free(size);
154 	id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
155 	if (id < 0) err("shmget");
156 
157 	struct shm_info i;
158 	if (shmctl(id, SHM_INFO, (void *)&i) < 0) err("shmctl");
159 	Dprintf("alloc %lu res %lu\n", i.shm_tot, i.shm_rss);
160 
161 
162 	Dprintf("id %d\n", id);
163 	char *map = shmat(id, NULL, 0600);
164 	if (map == (char*)-1) err("shmat");
165 
166 	shmctl(id, IPC_RMID, NULL);
167 
168 	memset(map, 0xff, size*NUM_PAGES);
169 	after = read_free(size);
170 
171 	Dprintf("before %lu after %lu diff %ld size %lu\n",
172 		before, after, before - after, size);
173 	assert(size == getpagesize() || (before - after) == NUM_PAGES);
174 	show(size);
175 	err = shmdt(map);
176 	assert(!err);
177 }
178 
179 void sanity_checks(void)
180 {
181 	int i;
182 	unsigned long largest = getpagesize();
183 
184 	for (i = 0; i < num_page_sizes; i++) {
185 		if (page_sizes[i] > largest)
186 			largest = page_sizes[i];
187 
188 		if (read_free(page_sizes[i]) < NUM_PAGES) {
189 			printf("Not enough huge pages for page size %lu MB, need %u\n",
190 				page_sizes[i] >> 20,
191 				NUM_PAGES);
192 			exit(0);
193 		}
194 	}
195 
196 	if (read_sysfs(0, "/proc/sys/kernel/shmmax") < NUM_PAGES * largest) {
197 		printf("Please do echo %lu > /proc/sys/kernel/shmmax", largest * NUM_PAGES);
198 		exit(0);
199 	}
200 
201 #if defined(__x86_64__)
202 	if (largest != 1U<<30) {
203 		printf("No GB pages available on x86-64\n"
204 		       "Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
205 		exit(0);
206 	}
207 #endif
208 }
209 
210 int main(void)
211 {
212 	int i;
213 	unsigned default_hps = default_huge_page_size();
214 
215 	find_pagesizes();
216 
217 	sanity_checks();
218 
219 	for (i = 0; i < num_page_sizes; i++) {
220 		unsigned long ps = page_sizes[i];
221 		int arg = ilog2(ps) << MAP_HUGE_SHIFT;
222 		printf("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
223 		test_mmap(ps, MAP_HUGETLB | arg);
224 	}
225 	printf("Testing default huge mmap\n");
226 	test_mmap(default_hps, MAP_HUGETLB);
227 
228 	puts("Testing non-huge shmget");
229 	test_shmget(getpagesize(), 0);
230 
231 	for (i = 0; i < num_page_sizes; i++) {
232 		unsigned long ps = page_sizes[i];
233 		int arg = ilog2(ps) << SHM_HUGE_SHIFT;
234 		printf("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
235 		test_shmget(ps, SHM_HUGETLB | arg);
236 	}
237 	puts("default huge shmget");
238 	test_shmget(default_hps, SHM_HUGETLB);
239 
240 	return 0;
241 }
242