1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * |
4 | * A test for the patch "Allow compaction of unevictable pages". |
5 | * With this patch we should be able to allocate at least 1/4 |
6 | * of RAM in huge pages. Without the patch much less is |
7 | * allocated. |
8 | */ |
9 | |
10 | #include <stdio.h> |
11 | #include <stdlib.h> |
12 | #include <sys/mman.h> |
13 | #include <sys/resource.h> |
14 | #include <fcntl.h> |
15 | #include <errno.h> |
16 | #include <unistd.h> |
17 | #include <string.h> |
18 | |
19 | #include "../kselftest.h" |
20 | |
21 | #define MAP_SIZE_MB 100 |
22 | #define MAP_SIZE (MAP_SIZE_MB * 1024 * 1024) |
23 | |
24 | struct map_list { |
25 | void *map; |
26 | struct map_list *next; |
27 | }; |
28 | |
29 | int read_memory_info(unsigned long *memfree, unsigned long *hugepagesize) |
30 | { |
31 | char buffer[256] = {0}; |
32 | char *cmd = "cat /proc/meminfo | grep -i memfree | grep -o '[0-9]*'" ; |
33 | FILE *cmdfile = popen(cmd, "r" ); |
34 | |
35 | if (!(fgets(buffer, sizeof(buffer), cmdfile))) { |
36 | ksft_print_msg(msg: "Failed to read meminfo: %s\n" , strerror(errno)); |
37 | return -1; |
38 | } |
39 | |
40 | pclose(cmdfile); |
41 | |
42 | *memfree = atoll(buffer); |
43 | cmd = "cat /proc/meminfo | grep -i hugepagesize | grep -o '[0-9]*'" ; |
44 | cmdfile = popen(cmd, "r" ); |
45 | |
46 | if (!(fgets(buffer, sizeof(buffer), cmdfile))) { |
47 | ksft_print_msg(msg: "Failed to read meminfo: %s\n" , strerror(errno)); |
48 | return -1; |
49 | } |
50 | |
51 | pclose(cmdfile); |
52 | *hugepagesize = atoll(buffer); |
53 | |
54 | return 0; |
55 | } |
56 | |
57 | int prereq(void) |
58 | { |
59 | char allowed; |
60 | int fd; |
61 | |
62 | fd = open("/proc/sys/vm/compact_unevictable_allowed" , |
63 | O_RDONLY | O_NONBLOCK); |
64 | if (fd < 0) { |
65 | ksft_print_msg(msg: "Failed to open /proc/sys/vm/compact_unevictable_allowed: %s\n" , |
66 | strerror(errno)); |
67 | return -1; |
68 | } |
69 | |
70 | if (read(fd, &allowed, sizeof(char)) != sizeof(char)) { |
71 | ksft_print_msg(msg: "Failed to read from /proc/sys/vm/compact_unevictable_allowed: %s\n" , |
72 | strerror(errno)); |
73 | close(fd); |
74 | return -1; |
75 | } |
76 | |
77 | close(fd); |
78 | if (allowed == '1') |
79 | return 0; |
80 | |
81 | ksft_print_msg(msg: "Compaction isn't allowed\n" ); |
82 | return -1; |
83 | } |
84 | |
85 | int check_compaction(unsigned long mem_free, unsigned int hugepage_size) |
86 | { |
87 | int fd, ret = -1; |
88 | int compaction_index = 0; |
89 | char initial_nr_hugepages[10] = {0}; |
90 | char nr_hugepages[10] = {0}; |
91 | |
92 | /* We want to test with 80% of available memory. Else, OOM killer comes |
93 | in to play */ |
94 | mem_free = mem_free * 0.8; |
95 | |
96 | fd = open("/proc/sys/vm/nr_hugepages" , O_RDWR | O_NONBLOCK); |
97 | if (fd < 0) { |
98 | ksft_print_msg(msg: "Failed to open /proc/sys/vm/nr_hugepages: %s\n" , |
99 | strerror(errno)); |
100 | ret = -1; |
101 | goto out; |
102 | } |
103 | |
104 | if (read(fd, initial_nr_hugepages, sizeof(initial_nr_hugepages)) <= 0) { |
105 | ksft_print_msg(msg: "Failed to read from /proc/sys/vm/nr_hugepages: %s\n" , |
106 | strerror(errno)); |
107 | goto close_fd; |
108 | } |
109 | |
110 | /* Start with the initial condition of 0 huge pages*/ |
111 | if (write(fd, "0" , sizeof(char)) != sizeof(char)) { |
112 | ksft_print_msg(msg: "Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n" , |
113 | strerror(errno)); |
114 | goto close_fd; |
115 | } |
116 | |
117 | lseek(fd, 0, SEEK_SET); |
118 | |
119 | /* Request a large number of huge pages. The Kernel will allocate |
120 | as much as it can */ |
121 | if (write(fd, "100000" , (6*sizeof(char))) != (6*sizeof(char))) { |
122 | ksft_print_msg(msg: "Failed to write 100000 to /proc/sys/vm/nr_hugepages: %s\n" , |
123 | strerror(errno)); |
124 | goto close_fd; |
125 | } |
126 | |
127 | lseek(fd, 0, SEEK_SET); |
128 | |
129 | if (read(fd, nr_hugepages, sizeof(nr_hugepages)) <= 0) { |
130 | ksft_print_msg(msg: "Failed to re-read from /proc/sys/vm/nr_hugepages: %s\n" , |
131 | strerror(errno)); |
132 | goto close_fd; |
133 | } |
134 | |
135 | /* We should have been able to request at least 1/3 rd of the memory in |
136 | huge pages */ |
137 | compaction_index = mem_free/(atoi(nr_hugepages) * hugepage_size); |
138 | |
139 | lseek(fd, 0, SEEK_SET); |
140 | |
141 | if (write(fd, initial_nr_hugepages, strlen(initial_nr_hugepages)) |
142 | != strlen(initial_nr_hugepages)) { |
143 | ksft_print_msg(msg: "Failed to write value to /proc/sys/vm/nr_hugepages: %s\n" , |
144 | strerror(errno)); |
145 | goto close_fd; |
146 | } |
147 | |
148 | ksft_print_msg(msg: "Number of huge pages allocated = %d\n" , |
149 | atoi(nr_hugepages)); |
150 | |
151 | if (compaction_index > 3) { |
152 | ksft_print_msg(msg: "ERROR: Less that 1/%d of memory is available\n" |
153 | "as huge pages\n" , compaction_index); |
154 | goto close_fd; |
155 | } |
156 | |
157 | ret = 0; |
158 | |
159 | close_fd: |
160 | close(fd); |
161 | out: |
162 | ksft_test_result(ret == 0, "check_compaction\n" ); |
163 | return ret; |
164 | } |
165 | |
166 | |
167 | int main(int argc, char **argv) |
168 | { |
169 | struct rlimit lim; |
170 | struct map_list *list = NULL, *entry; |
171 | size_t page_size, i; |
172 | void *map = NULL; |
173 | unsigned long mem_free = 0; |
174 | unsigned long hugepage_size = 0; |
175 | long mem_fragmentable_MB = 0; |
176 | |
177 | ksft_print_header(); |
178 | |
179 | if (prereq() || geteuid()) |
180 | return ksft_exit_skip(msg: "Prerequisites unsatisfied\n" ); |
181 | |
182 | ksft_set_plan(plan: 1); |
183 | |
184 | lim.rlim_cur = RLIM_INFINITY; |
185 | lim.rlim_max = RLIM_INFINITY; |
186 | if (setrlimit(RLIMIT_MEMLOCK, &lim)) |
187 | ksft_exit_fail_msg(msg: "Failed to set rlimit: %s\n" , strerror(errno)); |
188 | |
189 | page_size = getpagesize(); |
190 | |
191 | if (read_memory_info(memfree: &mem_free, hugepagesize: &hugepage_size) != 0) |
192 | ksft_exit_fail_msg(msg: "Failed to get meminfo\n" ); |
193 | |
194 | mem_fragmentable_MB = mem_free * 0.8 / 1024; |
195 | |
196 | while (mem_fragmentable_MB > 0) { |
197 | map = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, |
198 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_LOCKED, -1, 0); |
199 | if (map == MAP_FAILED) |
200 | break; |
201 | |
202 | entry = malloc(sizeof(struct map_list)); |
203 | if (!entry) { |
204 | munmap(map, MAP_SIZE); |
205 | break; |
206 | } |
207 | entry->map = map; |
208 | entry->next = list; |
209 | list = entry; |
210 | |
211 | /* Write something (in this case the address of the map) to |
212 | * ensure that KSM can't merge the mapped pages |
213 | */ |
214 | for (i = 0; i < MAP_SIZE; i += page_size) |
215 | *(unsigned long *)(map + i) = (unsigned long)map + i; |
216 | |
217 | mem_fragmentable_MB -= MAP_SIZE_MB; |
218 | } |
219 | |
220 | for (entry = list; entry != NULL; entry = entry->next) { |
221 | munmap(entry->map, MAP_SIZE); |
222 | if (!entry->next) |
223 | break; |
224 | entry = entry->next; |
225 | } |
226 | |
227 | if (check_compaction(mem_free, hugepage_size) == 0) |
228 | return ksft_exit_pass(); |
229 | |
230 | return ksft_exit_fail(); |
231 | } |
232 | |