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
ilog2(unsigned long v)55 int ilog2(unsigned long v)
56 {
57 int l = 0;
58 while ((1UL << l) < v)
59 l++;
60 return l;
61 }
62
find_pagesizes(void)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
show(unsigned long ps)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
read_sysfs(int warn,char * fmt,...)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
read_free(unsigned long ps)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
test_mmap(unsigned long size,unsigned flags)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
test_shmget(unsigned long size,unsigned flags)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
sanity_checks(void)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
main(void)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