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)))
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