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

source code of linux/tools/testing/selftests/mm/thuge-gen.c