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_sysfs(int warn,char * fmt,...)80 unsigned long read_sysfs(int warn, char *fmt, ...)
81 {
82 char *line = NULL;
83 size_t linelen = 0;
84 char buf[100];
85 FILE *f;
86 va_list ap;
87 unsigned long val = 0;
88
89 va_start(ap, fmt);
90 vsnprintf(buf, sizeof buf, fmt, ap);
91 va_end(ap);
92
93 f = fopen(buf, "r");
94 if (!f) {
95 if (warn)
96 ksft_print_msg("missing %s\n", buf);
97 return 0;
98 }
99 if (getline(&line, &linelen, f) > 0) {
100 sscanf(line, "%lu", &val);
101 }
102 fclose(f);
103 free(line);
104 return val;
105 }
106
read_free(unsigned long ps)107 unsigned long read_free(unsigned long ps)
108 {
109 return read_sysfs(ps != getpagesize(),
110 "/sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
111 ps >> 10);
112 }
113
test_mmap(unsigned long size,unsigned flags)114 void test_mmap(unsigned long size, unsigned flags)
115 {
116 char *map;
117 unsigned long before, after;
118
119 before = read_free(size);
120 map = mmap(NULL, size*NUM_PAGES, PROT_READ|PROT_WRITE,
121 MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|flags, -1, 0);
122 if (map == MAP_FAILED)
123 ksft_exit_fail_msg("mmap: %s\n", strerror(errno));
124
125 memset(map, 0xff, size*NUM_PAGES);
126 after = read_free(size);
127
128 show(size);
129 ksft_test_result(size == getpagesize() || (before - after) == NUM_PAGES,
130 "%s mmap\n", __func__);
131
132 if (munmap(map, size * NUM_PAGES))
133 ksft_exit_fail_msg("%s: unmap %s\n", __func__, strerror(errno));
134 }
135
test_shmget(unsigned long size,unsigned flags)136 void test_shmget(unsigned long size, unsigned flags)
137 {
138 int id;
139 unsigned long before, after;
140 struct shm_info i;
141 char *map;
142
143 before = read_free(size);
144 id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
145 if (id < 0) {
146 if (errno == EPERM) {
147 ksft_test_result_skip("shmget requires root privileges: %s\n",
148 strerror(errno));
149 return;
150 }
151 ksft_exit_fail_msg("shmget: %s\n", strerror(errno));
152 }
153
154 if (shmctl(id, SHM_INFO, (void *)&i) < 0)
155 ksft_exit_fail_msg("shmctl: %s\n", strerror(errno));
156
157 map = shmat(id, NULL, 0600);
158 if (map == MAP_FAILED)
159 ksft_exit_fail_msg("shmat: %s\n", strerror(errno));
160
161 shmctl(id, IPC_RMID, NULL);
162
163 memset(map, 0xff, size*NUM_PAGES);
164 after = read_free(size);
165
166 show(size);
167 ksft_test_result(size == getpagesize() || (before - after) == NUM_PAGES,
168 "%s: mmap\n", __func__);
169 if (shmdt(map))
170 ksft_exit_fail_msg("%s: shmdt: %s\n", __func__, strerror(errno));
171 }
172
find_pagesizes(void)173 void find_pagesizes(void)
174 {
175 unsigned long largest = getpagesize();
176 int i;
177 glob_t g;
178
179 glob("/sys/kernel/mm/hugepages/hugepages-*kB", 0, NULL, &g);
180 assert(g.gl_pathc <= NUM_PAGESIZES);
181 for (i = 0; (i < g.gl_pathc) && (num_page_sizes < NUM_PAGESIZES); i++) {
182 sscanf(g.gl_pathv[i], "/sys/kernel/mm/hugepages/hugepages-%lukB",
183 &page_sizes[num_page_sizes]);
184 page_sizes[num_page_sizes] <<= 10;
185 ksft_print_msg("Found %luMB\n", page_sizes[i] >> 20);
186
187 if (page_sizes[num_page_sizes] > largest)
188 largest = page_sizes[i];
189
190 if (read_free(page_sizes[num_page_sizes]) >= NUM_PAGES)
191 num_page_sizes++;
192 else
193 ksft_print_msg("SKIP for size %lu MB as not enough huge pages, need %u\n",
194 page_sizes[num_page_sizes] >> 20, NUM_PAGES);
195 }
196 globfree(&g);
197
198 if (read_sysfs(0, "/proc/sys/kernel/shmmax") < NUM_PAGES * largest)
199 ksft_exit_fail_msg("Please do echo %lu > /proc/sys/kernel/shmmax",
200 largest * NUM_PAGES);
201
202 #if defined(__x86_64__)
203 if (largest != 1U<<30) {
204 ksft_exit_fail_msg("No GB pages available on x86-64\n"
205 "Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
206 }
207 #endif
208 }
209
main(void)210 int main(void)
211 {
212 unsigned default_hps = default_huge_page_size();
213 int i;
214
215 ksft_print_header();
216
217 find_pagesizes();
218
219 if (!num_page_sizes)
220 ksft_finished();
221
222 ksft_set_plan(2 * num_page_sizes + 3);
223
224 for (i = 0; i < num_page_sizes; i++) {
225 unsigned long ps = page_sizes[i];
226 int arg = ilog2(ps) << MAP_HUGE_SHIFT;
227
228 ksft_print_msg("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
229 test_mmap(ps, MAP_HUGETLB | arg);
230 }
231
232 ksft_print_msg("Testing default huge mmap\n");
233 test_mmap(default_hps, MAP_HUGETLB);
234
235 ksft_print_msg("Testing non-huge shmget\n");
236 test_shmget(getpagesize(), 0);
237
238 for (i = 0; i < num_page_sizes; i++) {
239 unsigned long ps = page_sizes[i];
240 int arg = ilog2(ps) << SHM_HUGE_SHIFT;
241 ksft_print_msg("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
242 test_shmget(ps, SHM_HUGETLB | arg);
243 }
244
245 ksft_print_msg("default huge shmget\n");
246 test_shmget(default_hps, SHM_HUGETLB);
247
248 ksft_finished();
249 }
250