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 */
20static 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
64static 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
77static int set_nr_hugepages(long value)
78{
79 return set_file(path: "/proc/sys/vm/nr_hugepages", value);
80}
81
82static unsigned int check_first(char *addr)
83{
84 return *(unsigned int *)addr;
85}
86
87static 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
95static 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
164out_failed_munmap:
165 munmap(addr, LENGTH);
166 return ret;
167}
168
169static 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;
193out:
194 cg_destroy(cgroup: test_group);
195 free(test_group);
196 return ret;
197}
198
199int 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

source code of linux/tools/testing/selftests/cgroup/test_hugetlb_memcg.c