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(path: "/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(cgroup: test_group, control: "memory.current" ); |
103 | set_nr_hugepages(20); |
104 | current = cg_read_long(cgroup: test_group, control: "memory.current" ); |
105 | if (current - old_current >= MB(2)) { |
106 | ksft_print_msg( |
107 | msg: "setting nr_hugepages should not increase hugepage usage.\n" ); |
108 | ksft_print_msg(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(msg: "fail to mmap.\n" ); |
115 | return EXIT_FAILURE; |
116 | } |
117 | current = cg_read_long(cgroup: test_group, control: "memory.current" ); |
118 | if (current - old_current >= MB(2)) { |
119 | ksft_print_msg(msg: "mmap should not increase hugepage usage.\n" ); |
120 | ksft_print_msg(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(cgroup: test_group, control: "memory.current" ); |
129 | if (!values_close(a: expected_current, b: current, err: 5)) { |
130 | ksft_print_msg(msg: "memory usage should increase by around 2MB.\n" ); |
131 | ksft_print_msg( |
132 | msg: "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(cgroup: test_group, control: "memory.current" ); |
140 | expected_current = old_current + MB(8); |
141 | if (!values_close(a: expected_current, b: current, err: 5)) { |
142 | ksft_print_msg(msg: "memory usage should increase by around 8MB.\n" ); |
143 | ksft_print_msg( |
144 | msg: "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(cgroup: test_group, control: "memory.current" ); |
152 | expected_current = old_current; |
153 | if (!values_close(a: expected_current, b: current, err: 5)) { |
154 | ksft_print_msg(msg: "memory usage should go back down.\n" ); |
155 | ksft_print_msg( |
156 | msg: "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, name: "hugetlb_memcg_test" ); |
175 | if (!test_group || cg_create(cgroup: test_group)) { |
176 | ksft_print_msg(msg: "fail to create cgroup.\n" ); |
177 | goto out; |
178 | } |
179 | |
180 | if (cg_write(cgroup: test_group, control: "memory.max" , buf: "100M" )) { |
181 | ksft_print_msg(msg: "fail to set cgroup memory limit.\n" ); |
182 | goto out; |
183 | } |
184 | |
185 | /* disable swap */ |
186 | if (cg_write(cgroup: test_group, control: "memory.swap.max" , buf: "0" )) { |
187 | ksft_print_msg(msg: "fail to disable swap.\n" ); |
188 | goto out; |
189 | } |
190 | |
191 | if (!cg_run(cgroup: test_group, fn: hugetlb_test_program, arg: (void *)test_group)) |
192 | ret = KSFT_PASS; |
193 | out: |
194 | cg_destroy(cgroup: 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(option: "memory_hugetlb_accounting" ); |
205 | if (has_memory_hugetlb_acc < 0) |
206 | ksft_exit_skip(msg: "Failed to query cgroup mount option\n" ); |
207 | else if (!has_memory_hugetlb_acc) |
208 | ksft_exit_skip(msg: "memory hugetlb accounting is disabled\n" ); |
209 | |
210 | /* Unit is kB! */ |
211 | if (get_hugepage_size() != 2048) { |
212 | ksft_print_msg(msg: "test_hugetlb_memcg requires 2MB hugepages\n" ); |
213 | ksft_test_result_skip(msg: "test_hugetlb_memcg\n" ); |
214 | return ret; |
215 | } |
216 | |
217 | if (cg_find_unified_root(root, len: sizeof(root))) |
218 | ksft_exit_skip(msg: "cgroup v2 isn't mounted\n" ); |
219 | |
220 | switch (test_hugetlb_memcg(root)) { |
221 | case KSFT_PASS: |
222 | ksft_test_result_pass(msg: "test_hugetlb_memcg\n" ); |
223 | break; |
224 | case KSFT_SKIP: |
225 | ksft_test_result_skip(msg: "test_hugetlb_memcg\n" ); |
226 | break; |
227 | default: |
228 | ret = EXIT_FAILURE; |
229 | ksft_test_result_fail(msg: "test_hugetlb_memcg\n" ); |
230 | break; |
231 | } |
232 | |
233 | return ret; |
234 | } |
235 | |