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_exit_fail_msg("Please do echo %lu > /proc/sys/kernel/shmmax",
182 				   largest * NUM_PAGES);
183 
184 #if defined(__x86_64__)
185 	if (largest != 1U<<30) {
186 		ksft_exit_fail_msg("No GB pages available on x86-64\n"
187 				   "Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
188 	}
189 #endif
190 }
191 
main(void)192 int main(void)
193 {
194 	unsigned default_hps = default_huge_page_size();
195 	int i;
196 
197 	ksft_print_header();
198 
199 	find_pagesizes();
200 
201 	if (!num_page_sizes)
202 		ksft_finished();
203 
204 	ksft_set_plan(2 * num_page_sizes + 3);
205 
206 	for (i = 0; i < num_page_sizes; i++) {
207 		unsigned long ps = page_sizes[i];
208 		int arg = ilog2(ps) << MAP_HUGE_SHIFT;
209 
210 		ksft_print_msg("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
211 		test_mmap(ps, MAP_HUGETLB | arg);
212 	}
213 
214 	ksft_print_msg("Testing default huge mmap\n");
215 	test_mmap(default_hps, MAP_HUGETLB);
216 
217 	ksft_print_msg("Testing non-huge shmget\n");
218 	test_shmget(getpagesize(), 0);
219 
220 	for (i = 0; i < num_page_sizes; i++) {
221 		unsigned long ps = page_sizes[i];
222 		int arg = ilog2(ps) << SHM_HUGE_SHIFT;
223 		ksft_print_msg("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
224 		test_shmget(ps, SHM_HUGETLB | arg);
225 	}
226 
227 	ksft_print_msg("default huge shmget\n");
228 	test_shmget(default_hps, SHM_HUGETLB);
229 
230 	ksft_finished();
231 }
232