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 55 int ilog2(unsigned long v) 56 { 57 int l = 0; 58 while ((1UL << l) < v) 59 l++; 60 return l; 61 } 62 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 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 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 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 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 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 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 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