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 | |
48 | unsigned long page_sizes[NUM_PAGESIZES]; |
49 | int num_page_sizes; |
50 | |
51 | int ilog2(unsigned long v) |
52 | { |
53 | int l = 0; |
54 | while ((1UL << l) < v) |
55 | l++; |
56 | return l; |
57 | } |
58 | |
59 | void 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 | |
75 | unsigned 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 | |
102 | unsigned 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 | |
109 | void 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 | |
131 | void 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 | |
168 | void 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 | |
205 | int 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 | |