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 */
get_hugepage_size(void)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
set_file(const char * path,long value)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
set_nr_hugepages(long value)77 static int set_nr_hugepages(long value)
78 {
79 return set_file("/proc/sys/vm/nr_hugepages", value);
80 }
81
check_first(char * addr)82 static unsigned int check_first(char *addr)
83 {
84 return *(unsigned int *)addr;
85 }
86
write_data(char * addr)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
hugetlb_test_program(const char * cgroup,void * arg)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
test_hugetlb_memcg(char * root)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
main(int argc,char ** argv)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