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