xref: /linux/tools/testing/selftests/mm/compaction_test.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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 #include "hugepage_settings.h"
21 
22 #define MAP_SIZE_MB	100
23 #define MAP_SIZE	(MAP_SIZE_MB * 1024 * 1024)
24 
25 struct map_list {
26 	void *map;
27 	struct map_list *next;
28 };
29 
30 int read_memory_info(unsigned long *memfree, unsigned long *hugepagesize)
31 {
32 	char buffer[256];
33 	int found = 0;
34 	FILE *file;
35 	int ret = -1;
36 
37 	file = fopen("/proc/meminfo", "r");
38 	if (!file) {
39 		ksft_print_msg("Failed to open /proc/meminfo: %s\n",
40 			       strerror(errno));
41 		return -1;
42 	}
43 
44 	while (fgets(buffer, sizeof(buffer), file) && found != 2) {
45 		if (sscanf(buffer, "MemFree: %lu kB", memfree) == 1 ||
46 		    sscanf(buffer, "Hugepagesize: %lu kB", hugepagesize) == 1)
47 			found++;
48 	}
49 
50 	if (ferror(file))
51 		ksft_print_msg("Failed to read /proc/meminfo: %s\n",
52 			       strerror(errno));
53 	else if (found != 2)
54 		ksft_print_msg("Failed to parse /proc/meminfo\n");
55 	else
56 		ret = 0;
57 
58 	fclose(file);
59 	return ret;
60 }
61 
62 int prereq(void)
63 {
64 	char allowed;
65 	int fd;
66 
67 	fd = open("/proc/sys/vm/compact_unevictable_allowed",
68 		  O_RDONLY | O_NONBLOCK);
69 	if (fd < 0) {
70 		ksft_print_msg("Failed to open /proc/sys/vm/compact_unevictable_allowed: %s\n",
71 			       strerror(errno));
72 		return -1;
73 	}
74 
75 	if (read(fd, &allowed, sizeof(char)) != sizeof(char)) {
76 		ksft_print_msg("Failed to read from /proc/sys/vm/compact_unevictable_allowed: %s\n",
77 			       strerror(errno));
78 		close(fd);
79 		return -1;
80 	}
81 
82 	close(fd);
83 	if (allowed == '1')
84 		return 0;
85 
86 	ksft_print_msg("Compaction isn't allowed\n");
87 	return -1;
88 }
89 
90 int check_compaction(unsigned long mem_free, unsigned long hugepage_size)
91 {
92 	unsigned long nr_hugepages;
93 	int compaction_index = 0;
94 	int ret = -1;
95 
96 	/* We want to test with 80% of available memory. Else, OOM killer comes
97 	   in to play */
98 	mem_free = mem_free * 0.8;
99 
100 	/*
101 	 * Request huge pages for about half of the free memory. The Kernel
102 	 * will allocate as much as it can, and we expect it will get at least 1/3
103 	 */
104 	nr_hugepages = mem_free / hugepage_size / 2;
105 	hugetlb_set_nr_default_pages(nr_hugepages);
106 
107 	/* We should have been able to request at least 1/3 rd of the memory in
108 	   huge pages */
109 	nr_hugepages = hugetlb_nr_default_pages();
110 	if (!nr_hugepages) {
111 		ksft_print_msg("ERROR: No memory is available as huge pages\n");
112 		goto out;
113 	}
114 	compaction_index = mem_free/(nr_hugepages * hugepage_size);
115 
116 	ksft_print_msg("Number of huge pages allocated = %lu\n", nr_hugepages);
117 
118 	if (compaction_index > 3) {
119 		ksft_print_msg("ERROR: Less than 1/%d of memory is available\n"
120 			       "as huge pages\n", compaction_index);
121 		goto out;
122 	}
123 
124 	ret = 0;
125 
126  out:
127 	ksft_test_result(ret == 0, "check_compaction\n");
128 	return ret;
129 }
130 
131 int main(int argc, char **argv)
132 {
133 	struct rlimit lim;
134 	struct map_list *list = NULL, *entry;
135 	size_t page_size, i;
136 	void *map = NULL;
137 	unsigned long mem_free = 0;
138 	unsigned long hugepage_size = 0;
139 	long mem_fragmentable_MB = 0;
140 
141 	ksft_print_header();
142 
143 	if (prereq() || geteuid())
144 		ksft_exit_skip("Prerequisites unsatisfied\n");
145 
146 	/* Start the test without hugepages reducing mem_free */
147 	if (!hugetlb_setup_default_exact(0))
148 		ksft_exit_skip("Could not reset nr_hugepages\n");
149 
150 	ksft_set_plan(1);
151 
152 	lim.rlim_cur = RLIM_INFINITY;
153 	lim.rlim_max = RLIM_INFINITY;
154 	if (setrlimit(RLIMIT_MEMLOCK, &lim))
155 		ksft_exit_fail_msg("Failed to set rlimit: %s\n", strerror(errno));
156 
157 	page_size = getpagesize();
158 
159 	if (read_memory_info(&mem_free, &hugepage_size) != 0)
160 		ksft_exit_fail_msg("Failed to get meminfo\n");
161 
162 	mem_fragmentable_MB = mem_free * 0.8 / 1024;
163 
164 	while (mem_fragmentable_MB > 0) {
165 		map = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE,
166 			   MAP_ANONYMOUS | MAP_PRIVATE | MAP_LOCKED, -1, 0);
167 		if (map == MAP_FAILED)
168 			break;
169 
170 		entry = malloc(sizeof(struct map_list));
171 		if (!entry) {
172 			munmap(map, MAP_SIZE);
173 			break;
174 		}
175 		entry->map = map;
176 		entry->next = list;
177 		list = entry;
178 
179 		/* Write something (in this case the address of the map) to
180 		 * ensure that KSM can't merge the mapped pages
181 		 */
182 		for (i = 0; i < MAP_SIZE; i += page_size)
183 			*(unsigned long *)(map + i) = (unsigned long)map + i;
184 
185 		mem_fragmentable_MB -= MAP_SIZE_MB;
186 	}
187 
188 	/* Unmap every other entry in the list to create fragmentation with
189 	 * locked pages before invoking check_compaction().
190 	 */
191 	for (entry = list; entry != NULL; entry = entry->next) {
192 		munmap(entry->map, MAP_SIZE);
193 		if (!entry->next)
194 			break;
195 		entry = entry->next;
196 	}
197 
198 	if (check_compaction(mem_free, hugepage_size) == 0)
199 		ksft_exit_pass();
200 
201 	ksft_exit_fail();
202 }
203