1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * 4 * A test for the patch "Allow compaction of unevictable pages". 5 * With this patch we should be able to allocate at least 1/4 6 * of RAM in huge pages. Without the patch much less is 7 * allocated. 8 */ 9 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <sys/mman.h> 13 #include <sys/resource.h> 14 #include <fcntl.h> 15 #include <errno.h> 16 #include <unistd.h> 17 #include <string.h> 18 19 #include "../kselftest.h" 20 21 #define MAP_SIZE_MB 100 22 #define MAP_SIZE (MAP_SIZE_MB * 1024 * 1024) 23 24 struct map_list { 25 void *map; 26 struct map_list *next; 27 }; 28 29 int read_memory_info(unsigned long *memfree, unsigned long *hugepagesize) 30 { 31 char buffer[256] = {0}; 32 char *cmd = "cat /proc/meminfo | grep -i memfree | grep -o '[0-9]*'"; 33 FILE *cmdfile = popen(cmd, "r"); 34 35 if (!(fgets(buffer, sizeof(buffer), cmdfile))) { 36 ksft_print_msg("Failed to read meminfo: %s\n", strerror(errno)); 37 return -1; 38 } 39 40 pclose(cmdfile); 41 42 *memfree = atoll(buffer); 43 cmd = "cat /proc/meminfo | grep -i hugepagesize | grep -o '[0-9]*'"; 44 cmdfile = popen(cmd, "r"); 45 46 if (!(fgets(buffer, sizeof(buffer), cmdfile))) { 47 ksft_print_msg("Failed to read meminfo: %s\n", strerror(errno)); 48 return -1; 49 } 50 51 pclose(cmdfile); 52 *hugepagesize = atoll(buffer); 53 54 return 0; 55 } 56 57 int prereq(void) 58 { 59 char allowed; 60 int fd; 61 62 fd = open("/proc/sys/vm/compact_unevictable_allowed", 63 O_RDONLY | O_NONBLOCK); 64 if (fd < 0) { 65 ksft_print_msg("Failed to open /proc/sys/vm/compact_unevictable_allowed: %s\n", 66 strerror(errno)); 67 return -1; 68 } 69 70 if (read(fd, &allowed, sizeof(char)) != sizeof(char)) { 71 ksft_print_msg("Failed to read from /proc/sys/vm/compact_unevictable_allowed: %s\n", 72 strerror(errno)); 73 close(fd); 74 return -1; 75 } 76 77 close(fd); 78 if (allowed == '1') 79 return 0; 80 81 ksft_print_msg("Compaction isn't allowed\n"); 82 return -1; 83 } 84 85 int check_compaction(unsigned long mem_free, unsigned long hugepage_size, 86 unsigned long initial_nr_hugepages) 87 { 88 unsigned long nr_hugepages_ul; 89 int fd, ret = -1; 90 int compaction_index = 0; 91 char nr_hugepages[20] = {0}; 92 char init_nr_hugepages[24] = {0}; 93 char target_nr_hugepages[24] = {0}; 94 int slen; 95 96 snprintf(init_nr_hugepages, sizeof(init_nr_hugepages), 97 "%lu", initial_nr_hugepages); 98 99 /* We want to test with 80% of available memory. Else, OOM killer comes 100 in to play */ 101 mem_free = mem_free * 0.8; 102 103 fd = open("/proc/sys/vm/nr_hugepages", O_RDWR | O_NONBLOCK); 104 if (fd < 0) { 105 ksft_print_msg("Failed to open /proc/sys/vm/nr_hugepages: %s\n", 106 strerror(errno)); 107 ret = -1; 108 goto out; 109 } 110 111 /* 112 * Request huge pages for about half of the free memory. The Kernel 113 * will allocate as much as it can, and we expect it will get at least 1/3 114 */ 115 nr_hugepages_ul = mem_free / hugepage_size / 2; 116 snprintf(target_nr_hugepages, sizeof(target_nr_hugepages), 117 "%lu", nr_hugepages_ul); 118 119 slen = strlen(target_nr_hugepages); 120 if (write(fd, target_nr_hugepages, slen) != slen) { 121 ksft_print_msg("Failed to write %lu to /proc/sys/vm/nr_hugepages: %s\n", 122 nr_hugepages_ul, strerror(errno)); 123 goto close_fd; 124 } 125 126 lseek(fd, 0, SEEK_SET); 127 128 if (read(fd, nr_hugepages, sizeof(nr_hugepages)) <= 0) { 129 ksft_print_msg("Failed to re-read from /proc/sys/vm/nr_hugepages: %s\n", 130 strerror(errno)); 131 goto close_fd; 132 } 133 134 /* We should have been able to request at least 1/3 rd of the memory in 135 huge pages */ 136 nr_hugepages_ul = strtoul(nr_hugepages, NULL, 10); 137 if (!nr_hugepages_ul) { 138 ksft_print_msg("ERROR: No memory is available as huge pages\n"); 139 goto close_fd; 140 } 141 compaction_index = mem_free/(nr_hugepages_ul * hugepage_size); 142 143 lseek(fd, 0, SEEK_SET); 144 145 if (write(fd, init_nr_hugepages, strlen(init_nr_hugepages)) 146 != strlen(init_nr_hugepages)) { 147 ksft_print_msg("Failed to write value to /proc/sys/vm/nr_hugepages: %s\n", 148 strerror(errno)); 149 goto close_fd; 150 } 151 152 ksft_print_msg("Number of huge pages allocated = %lu\n", 153 nr_hugepages_ul); 154 155 if (compaction_index > 3) { 156 ksft_print_msg("ERROR: Less than 1/%d of memory is available\n" 157 "as huge pages\n", compaction_index); 158 goto close_fd; 159 } 160 161 ret = 0; 162 163 close_fd: 164 close(fd); 165 out: 166 ksft_test_result(ret == 0, "check_compaction\n"); 167 return ret; 168 } 169 170 int set_zero_hugepages(unsigned long *initial_nr_hugepages) 171 { 172 int fd, ret = -1; 173 char nr_hugepages[20] = {0}; 174 175 fd = open("/proc/sys/vm/nr_hugepages", O_RDWR | O_NONBLOCK); 176 if (fd < 0) { 177 ksft_print_msg("Failed to open /proc/sys/vm/nr_hugepages: %s\n", 178 strerror(errno)); 179 goto out; 180 } 181 if (read(fd, nr_hugepages, sizeof(nr_hugepages)) <= 0) { 182 ksft_print_msg("Failed to read from /proc/sys/vm/nr_hugepages: %s\n", 183 strerror(errno)); 184 goto close_fd; 185 } 186 187 lseek(fd, 0, SEEK_SET); 188 189 /* Start with the initial condition of 0 huge pages */ 190 if (write(fd, "0", sizeof(char)) != sizeof(char)) { 191 ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n", 192 strerror(errno)); 193 goto close_fd; 194 } 195 196 *initial_nr_hugepages = strtoul(nr_hugepages, NULL, 10); 197 ret = 0; 198 199 close_fd: 200 close(fd); 201 202 out: 203 return ret; 204 } 205 206 int main(int argc, char **argv) 207 { 208 struct rlimit lim; 209 struct map_list *list = NULL, *entry; 210 size_t page_size, i; 211 void *map = NULL; 212 unsigned long mem_free = 0; 213 unsigned long hugepage_size = 0; 214 long mem_fragmentable_MB = 0; 215 unsigned long initial_nr_hugepages; 216 217 ksft_print_header(); 218 219 if (prereq() || geteuid()) 220 ksft_exit_skip("Prerequisites unsatisfied\n"); 221 222 ksft_set_plan(1); 223 224 /* Start the test without hugepages reducing mem_free */ 225 if (set_zero_hugepages(&initial_nr_hugepages)) 226 ksft_exit_fail(); 227 228 lim.rlim_cur = RLIM_INFINITY; 229 lim.rlim_max = RLIM_INFINITY; 230 if (setrlimit(RLIMIT_MEMLOCK, &lim)) 231 ksft_exit_fail_msg("Failed to set rlimit: %s\n", strerror(errno)); 232 233 page_size = getpagesize(); 234 235 if (read_memory_info(&mem_free, &hugepage_size) != 0) 236 ksft_exit_fail_msg("Failed to get meminfo\n"); 237 238 mem_fragmentable_MB = mem_free * 0.8 / 1024; 239 240 while (mem_fragmentable_MB > 0) { 241 map = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, 242 MAP_ANONYMOUS | MAP_PRIVATE | MAP_LOCKED, -1, 0); 243 if (map == MAP_FAILED) 244 break; 245 246 entry = malloc(sizeof(struct map_list)); 247 if (!entry) { 248 munmap(map, MAP_SIZE); 249 break; 250 } 251 entry->map = map; 252 entry->next = list; 253 list = entry; 254 255 /* Write something (in this case the address of the map) to 256 * ensure that KSM can't merge the mapped pages 257 */ 258 for (i = 0; i < MAP_SIZE; i += page_size) 259 *(unsigned long *)(map + i) = (unsigned long)map + i; 260 261 mem_fragmentable_MB -= MAP_SIZE_MB; 262 } 263 264 for (entry = list; entry != NULL; entry = entry->next) { 265 munmap(entry->map, MAP_SIZE); 266 if (!entry->next) 267 break; 268 entry = entry->next; 269 } 270 271 if (check_compaction(mem_free, hugepage_size, 272 initial_nr_hugepages) == 0) 273 ksft_exit_pass(); 274 275 ksft_exit_fail(); 276 } 277