1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 4 #include <linux/limits.h> 5 #include <sys/mman.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <fcntl.h> 10 #include "../kselftest.h" 11 #include "cgroup_util.h" 12 13 #define ADDR ((void *)(0x0UL)) 14 #define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB) 15 /* mapping 8 MBs == 4 hugepages */ 16 #define LENGTH (8UL*1024*1024) 17 #define PROTECTION (PROT_READ | PROT_WRITE) 18 19 /* borrowed from mm/hmm-tests.c */ 20 static long get_hugepage_size(void) 21 { 22 int fd; 23 char buf[2048]; 24 int len; 25 char *p, *q, *path = "/proc/meminfo", *tag = "Hugepagesize:"; 26 long val; 27 28 fd = open(path, O_RDONLY); 29 if (fd < 0) { 30 /* Error opening the file */ 31 return -1; 32 } 33 34 len = read(fd, buf, sizeof(buf)); 35 close(fd); 36 if (len < 0) { 37 /* Error in reading the file */ 38 return -1; 39 } 40 if (len == sizeof(buf)) { 41 /* Error file is too large */ 42 return -1; 43 } 44 buf[len] = '\0'; 45 46 /* Search for a tag if provided */ 47 if (tag) { 48 p = strstr(buf, tag); 49 if (!p) 50 return -1; /* looks like the line we want isn't there */ 51 p += strlen(tag); 52 } else 53 p = buf; 54 55 val = strtol(p, &q, 0); 56 if (*q != ' ') { 57 /* Error parsing the file */ 58 return -1; 59 } 60 61 return val; 62 } 63 64 static int set_file(const char *path, long value) 65 { 66 FILE *file; 67 int ret; 68 69 file = fopen(path, "w"); 70 if (!file) 71 return -1; 72 ret = fprintf(file, "%ld\n", value); 73 fclose(file); 74 return ret; 75 } 76 77 static int set_nr_hugepages(long value) 78 { 79 return set_file("/proc/sys/vm/nr_hugepages", value); 80 } 81 82 static unsigned int check_first(char *addr) 83 { 84 return *(unsigned int *)addr; 85 } 86 87 static void write_data(char *addr) 88 { 89 unsigned long i; 90 91 for (i = 0; i < LENGTH; i++) 92 *(addr + i) = (char)i; 93 } 94 95 static int hugetlb_test_program(const char *cgroup, void *arg) 96 { 97 char *test_group = (char *)arg; 98 void *addr; 99 long old_current, expected_current, current; 100 int ret = EXIT_FAILURE; 101 102 old_current = cg_read_long(test_group, "memory.current"); 103 set_nr_hugepages(20); 104 current = cg_read_long(test_group, "memory.current"); 105 if (current - old_current >= MB(2)) { 106 ksft_print_msg( 107 "setting nr_hugepages should not increase hugepage usage.\n"); 108 ksft_print_msg("before: %ld, after: %ld\n", old_current, current); 109 return EXIT_FAILURE; 110 } 111 112 addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, 0, 0); 113 if (addr == MAP_FAILED) { 114 ksft_print_msg("fail to mmap.\n"); 115 return EXIT_FAILURE; 116 } 117 current = cg_read_long(test_group, "memory.current"); 118 if (current - old_current >= MB(2)) { 119 ksft_print_msg("mmap should not increase hugepage usage.\n"); 120 ksft_print_msg("before: %ld, after: %ld\n", old_current, current); 121 goto out_failed_munmap; 122 } 123 old_current = current; 124 125 /* read the first page */ 126 check_first(addr); 127 expected_current = old_current + MB(2); 128 current = cg_read_long(test_group, "memory.current"); 129 if (!values_close(expected_current, current, 5)) { 130 ksft_print_msg("memory usage should increase by around 2MB.\n"); 131 ksft_print_msg( 132 "expected memory: %ld, actual memory: %ld\n", 133 expected_current, current); 134 goto out_failed_munmap; 135 } 136 137 /* write to the whole range */ 138 write_data(addr); 139 current = cg_read_long(test_group, "memory.current"); 140 expected_current = old_current + MB(8); 141 if (!values_close(expected_current, current, 5)) { 142 ksft_print_msg("memory usage should increase by around 8MB.\n"); 143 ksft_print_msg( 144 "expected memory: %ld, actual memory: %ld\n", 145 expected_current, current); 146 goto out_failed_munmap; 147 } 148 149 /* unmap the whole range */ 150 munmap(addr, LENGTH); 151 current = cg_read_long(test_group, "memory.current"); 152 expected_current = old_current; 153 if (!values_close(expected_current, current, 5)) { 154 ksft_print_msg("memory usage should go back down.\n"); 155 ksft_print_msg( 156 "expected memory: %ld, actual memory: %ld\n", 157 expected_current, current); 158 return ret; 159 } 160 161 ret = EXIT_SUCCESS; 162 return ret; 163 164 out_failed_munmap: 165 munmap(addr, LENGTH); 166 return ret; 167 } 168 169 static int test_hugetlb_memcg(char *root) 170 { 171 int ret = KSFT_FAIL; 172 char *test_group; 173 174 test_group = cg_name(root, "hugetlb_memcg_test"); 175 if (!test_group || cg_create(test_group)) { 176 ksft_print_msg("fail to create cgroup.\n"); 177 goto out; 178 } 179 180 if (cg_write(test_group, "memory.max", "100M")) { 181 ksft_print_msg("fail to set cgroup memory limit.\n"); 182 goto out; 183 } 184 185 /* disable swap */ 186 if (cg_write(test_group, "memory.swap.max", "0")) { 187 ksft_print_msg("fail to disable swap.\n"); 188 goto out; 189 } 190 191 if (!cg_run(test_group, hugetlb_test_program, (void *)test_group)) 192 ret = KSFT_PASS; 193 out: 194 cg_destroy(test_group); 195 free(test_group); 196 return ret; 197 } 198 199 int main(int argc, char **argv) 200 { 201 char root[PATH_MAX]; 202 int ret = EXIT_SUCCESS, has_memory_hugetlb_acc; 203 204 has_memory_hugetlb_acc = proc_mount_contains("memory_hugetlb_accounting"); 205 if (has_memory_hugetlb_acc < 0) 206 ksft_exit_skip("Failed to query cgroup mount option\n"); 207 else if (!has_memory_hugetlb_acc) 208 ksft_exit_skip("memory hugetlb accounting is disabled\n"); 209 210 /* Unit is kB! */ 211 if (get_hugepage_size() != 2048) { 212 ksft_print_msg("test_hugetlb_memcg requires 2MB hugepages\n"); 213 ksft_test_result_skip("test_hugetlb_memcg\n"); 214 return ret; 215 } 216 217 if (cg_find_unified_root(root, sizeof(root), NULL)) 218 ksft_exit_skip("cgroup v2 isn't mounted\n"); 219 220 switch (test_hugetlb_memcg(root)) { 221 case KSFT_PASS: 222 ksft_test_result_pass("test_hugetlb_memcg\n"); 223 break; 224 case KSFT_SKIP: 225 ksft_test_result_skip("test_hugetlb_memcg\n"); 226 break; 227 default: 228 ret = EXIT_FAILURE; 229 ksft_test_result_fail("test_hugetlb_memcg\n"); 230 break; 231 } 232 233 return ret; 234 } 235