xref: /linux/tools/testing/selftests/mm/thuge-gen.c (revision 8804d970fab45726b3c7cd7f240b31122aa94219)
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. 1GB wouldn't be tested if it isn't available.
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
17 #include <sys/mman.h>
18 #include <linux/mman.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <sys/ipc.h>
22 #include <sys/shm.h>
23 #include <sys/stat.h>
24 #include <glob.h>
25 #include <assert.h>
26 #include <unistd.h>
27 #include <stdarg.h>
28 #include <string.h>
29 #include "vm_util.h"
30 #include "../kselftest.h"
31 
32 #if !defined(MAP_HUGETLB)
33 #define MAP_HUGETLB	0x40000
34 #endif
35 
36 #define SHM_HUGETLB     04000   /* segment will use huge TLB pages */
37 #ifndef SHM_HUGE_SHIFT
38 #define SHM_HUGE_SHIFT  26
39 #endif
40 #ifndef SHM_HUGE_MASK
41 #define SHM_HUGE_MASK   0x3f
42 #endif
43 #ifndef SHM_HUGE_2MB
44 #define SHM_HUGE_2MB    (21 << SHM_HUGE_SHIFT)
45 #endif
46 #ifndef SHM_HUGE_1GB
47 #define SHM_HUGE_1GB    (30 << SHM_HUGE_SHIFT)
48 #endif
49 
50 #define NUM_PAGESIZES   5
51 #define NUM_PAGES 4
52 
53 unsigned long page_sizes[NUM_PAGESIZES];
54 int num_page_sizes;
55 
ilog2(unsigned long v)56 int ilog2(unsigned long v)
57 {
58 	int l = 0;
59 	while ((1UL << l) < v)
60 		l++;
61 	return l;
62 }
63 
show(unsigned long ps)64 void show(unsigned long ps)
65 {
66 	char buf[100];
67 
68 	if (ps == getpagesize())
69 		return;
70 
71 	ksft_print_msg("%luMB: ", ps >> 20);
72 
73 	fflush(stdout);
74 	snprintf(buf, sizeof buf,
75 		"cat /sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
76 		ps >> 10);
77 	system(buf);
78 }
79 
read_free(unsigned long ps)80 unsigned long read_free(unsigned long ps)
81 {
82 	unsigned long val = 0;
83 	char buf[100];
84 
85 	snprintf(buf, sizeof(buf),
86 		 "/sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
87 		 ps >> 10);
88 	if (read_sysfs(buf, &val) && ps != getpagesize())
89 		ksft_print_msg("missing %s\n", buf);
90 
91 	return val;
92 }
93 
test_mmap(unsigned long size,unsigned flags)94 void test_mmap(unsigned long size, unsigned flags)
95 {
96 	char *map;
97 	unsigned long before, after;
98 
99 	before = read_free(size);
100 	map = mmap(NULL, size*NUM_PAGES, PROT_READ|PROT_WRITE,
101 			MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|flags, -1, 0);
102 	if (map == MAP_FAILED)
103 		ksft_exit_fail_msg("mmap: %s\n", strerror(errno));
104 
105 	memset(map, 0xff, size*NUM_PAGES);
106 	after = read_free(size);
107 
108 	show(size);
109 	ksft_test_result(size == getpagesize() || (before - after) == NUM_PAGES,
110 			 "%s mmap %lu %x\n", __func__, size, flags);
111 
112 	if (munmap(map, size * NUM_PAGES))
113 		ksft_exit_fail_msg("%s: unmap %s\n", __func__, strerror(errno));
114 }
115 
test_shmget(unsigned long size,unsigned flags)116 void test_shmget(unsigned long size, unsigned flags)
117 {
118 	int id;
119 	unsigned long before, after;
120 	struct shm_info i;
121 	char *map;
122 
123 	before = read_free(size);
124 	id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
125 	if (id < 0) {
126 		if (errno == EPERM) {
127 			ksft_test_result_skip("shmget requires root privileges: %s\n",
128 					      strerror(errno));
129 			return;
130 		}
131 		ksft_exit_fail_msg("shmget: %s\n", strerror(errno));
132 	}
133 
134 	if (shmctl(id, SHM_INFO, (void *)&i) < 0)
135 		ksft_exit_fail_msg("shmctl: %s\n", strerror(errno));
136 
137 	map = shmat(id, NULL, 0600);
138 	if (map == MAP_FAILED)
139 		ksft_exit_fail_msg("shmat: %s\n", strerror(errno));
140 
141 	shmctl(id, IPC_RMID, NULL);
142 
143 	memset(map, 0xff, size*NUM_PAGES);
144 	after = read_free(size);
145 
146 	show(size);
147 	ksft_test_result(size == getpagesize() || (before - after) == NUM_PAGES,
148 			 "%s: mmap %lu %x\n", __func__, size, flags);
149 	if (shmdt(map))
150 		ksft_exit_fail_msg("%s: shmdt: %s\n", __func__, strerror(errno));
151 }
152 
find_pagesizes(void)153 void find_pagesizes(void)
154 {
155 	unsigned long largest = getpagesize();
156 	unsigned long shmmax_val = 0;
157 	int i;
158 	glob_t g;
159 
160 	glob("/sys/kernel/mm/hugepages/hugepages-*kB", 0, NULL, &g);
161 	assert(g.gl_pathc <= NUM_PAGESIZES);
162 	for (i = 0; (i < g.gl_pathc) && (num_page_sizes < NUM_PAGESIZES); i++) {
163 		sscanf(g.gl_pathv[i], "/sys/kernel/mm/hugepages/hugepages-%lukB",
164 				&page_sizes[num_page_sizes]);
165 		page_sizes[num_page_sizes] <<= 10;
166 		ksft_print_msg("Found %luMB\n", page_sizes[i] >> 20);
167 
168 		if (page_sizes[num_page_sizes] > largest)
169 			largest = page_sizes[i];
170 
171 		if (read_free(page_sizes[num_page_sizes]) >= NUM_PAGES)
172 			num_page_sizes++;
173 		else
174 			ksft_print_msg("SKIP for size %lu MB as not enough huge pages, need %u\n",
175 				       page_sizes[num_page_sizes] >> 20, NUM_PAGES);
176 	}
177 	globfree(&g);
178 
179 	read_sysfs("/proc/sys/kernel/shmmax", &shmmax_val);
180 	if (shmmax_val < NUM_PAGES * largest) {
181 		ksft_print_msg("WARNING: shmmax is too small to run this test.\n");
182 		ksft_print_msg("Please run the following command to increase shmmax:\n");
183 		ksft_print_msg("echo %lu > /proc/sys/kernel/shmmax\n", largest * NUM_PAGES);
184 		ksft_exit_skip("Test skipped due to insufficient shmmax value.\n");
185 	}
186 
187 #if defined(__x86_64__)
188 	if (largest != 1U<<30) {
189 		ksft_exit_skip("No GB pages available on x86-64\n"
190 				   "Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
191 	}
192 #endif
193 }
194 
main(void)195 int main(void)
196 {
197 	unsigned default_hps = default_huge_page_size();
198 	int i;
199 
200 	ksft_print_header();
201 
202 	find_pagesizes();
203 
204 	if (!num_page_sizes)
205 		ksft_finished();
206 
207 	ksft_set_plan(2 * num_page_sizes + 3);
208 
209 	for (i = 0; i < num_page_sizes; i++) {
210 		unsigned long ps = page_sizes[i];
211 		int arg = ilog2(ps) << MAP_HUGE_SHIFT;
212 
213 		ksft_print_msg("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
214 		test_mmap(ps, MAP_HUGETLB | arg);
215 	}
216 
217 	ksft_print_msg("Testing default huge mmap\n");
218 	test_mmap(default_hps, MAP_HUGETLB);
219 
220 	ksft_print_msg("Testing non-huge shmget\n");
221 	test_shmget(getpagesize(), 0);
222 
223 	for (i = 0; i < num_page_sizes; i++) {
224 		unsigned long ps = page_sizes[i];
225 		int arg = ilog2(ps) << SHM_HUGE_SHIFT;
226 		ksft_print_msg("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
227 		test_shmget(ps, SHM_HUGETLB | arg);
228 	}
229 
230 	ksft_print_msg("default huge shmget\n");
231 	test_shmget(default_hps, SHM_HUGETLB);
232 
233 	ksft_finished();
234 }
235