selftests/mm: compaction_test: fix bogus test success and reduce probability of OOM-killer invocation

Reset nr_hugepages to zero before the start of the test.

If a non-zero number of hugepages is already set before the start of the
test, the following problems arise:

- The probability of the test getting OOM-killed increases. Proof:
The test wants to run on 80% of available memory to prevent OOM-killing
(see original code comments). Let the value of mem_free at the start
of the test, when nr_hugepages = 0, be x. In the other case, when
nr_hugepages > 0, let the memory consumed by hugepages be y. In the
former case, the test operates on 0.8 * x of memory. In the latter,
the test operates on 0.8 * (x - y) of memory, with y already filled,
hence, memory consumed is y + 0.8 * (x - y) = 0.8 * x + 0.2 * y > 0.8 *
x. Q.E.D

- The probability of a bogus test success increases. Proof: Let the
memory consumed by hugepages be greater than 25% of x, with x and y
defined as above. The definition of compaction_index is c_index = (x -
y)/z where z is the memory consumed by hugepages after trying to
increase them again. In check_compaction(), we set the number of
hugepages to zero, and then increase them back; the probability that
they will be set back to consume at least y amount of memory again is
very high (since there is not much delay between the two attempts of
changing nr_hugepages). Hence, z >= y > (x/4) (by the 25% assumption).
Therefore, c_index = (x - y)/z <= (x - y)/y = x/y - 1 < 4 - 1 = 3
hence, c_index can always be forced to be less than 3, thereby the test
succeeding always. Q.E.D

Link: https://lkml.kernel.org/r/20240521074358.675031-4-dev.jain@arm.com
Fixes: bd67d5c15cc1 ("Test compaction of mlocked memory")
Signed-off-by: Dev Jain <dev.jain@arm.com>
Cc: <stable@vger.kernel.org>
Cc: Anshuman Khandual <anshuman.khandual@arm.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Sri Jayaramappa <sjayaram@akamai.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by Dev Jain and committed by Andrew Morton fb9293b6 9ad665ef

+49 -22
+49 -22
tools/testing/selftests/mm/compaction_test.c
··· 82 82 return -1; 83 83 } 84 84 85 - int check_compaction(unsigned long mem_free, unsigned long hugepage_size) 85 + int check_compaction(unsigned long mem_free, unsigned long hugepage_size, 86 + unsigned long initial_nr_hugepages) 86 87 { 87 88 unsigned long nr_hugepages_ul; 88 89 int fd, ret = -1; 89 90 int compaction_index = 0; 90 - char initial_nr_hugepages[20] = {0}; 91 91 char nr_hugepages[20] = {0}; 92 + char init_nr_hugepages[20] = {0}; 93 + 94 + sprintf(init_nr_hugepages, "%lu", initial_nr_hugepages); 92 95 93 96 /* We want to test with 80% of available memory. Else, OOM killer comes 94 97 in to play */ ··· 104 101 ret = -1; 105 102 goto out; 106 103 } 107 - 108 - if (read(fd, initial_nr_hugepages, sizeof(initial_nr_hugepages)) <= 0) { 109 - ksft_print_msg("Failed to read from /proc/sys/vm/nr_hugepages: %s\n", 110 - strerror(errno)); 111 - goto close_fd; 112 - } 113 - 114 - lseek(fd, 0, SEEK_SET); 115 - 116 - /* Start with the initial condition of 0 huge pages*/ 117 - if (write(fd, "0", sizeof(char)) != sizeof(char)) { 118 - ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n", 119 - strerror(errno)); 120 - goto close_fd; 121 - } 122 - 123 - lseek(fd, 0, SEEK_SET); 124 104 125 105 /* Request a large number of huge pages. The Kernel will allocate 126 106 as much as it can */ ··· 132 146 133 147 lseek(fd, 0, SEEK_SET); 134 148 135 - if (write(fd, initial_nr_hugepages, strlen(initial_nr_hugepages)) 136 - != strlen(initial_nr_hugepages)) { 149 + if (write(fd, init_nr_hugepages, strlen(init_nr_hugepages)) 150 + != strlen(init_nr_hugepages)) { 137 151 ksft_print_msg("Failed to write value to /proc/sys/vm/nr_hugepages: %s\n", 138 152 strerror(errno)); 139 153 goto close_fd; ··· 157 171 return ret; 158 172 } 159 173 174 + int set_zero_hugepages(unsigned long *initial_nr_hugepages) 175 + { 176 + int fd, ret = -1; 177 + char nr_hugepages[20] = {0}; 178 + 179 + fd = open("/proc/sys/vm/nr_hugepages", O_RDWR | O_NONBLOCK); 180 + if (fd < 0) { 181 + ksft_print_msg("Failed to open /proc/sys/vm/nr_hugepages: %s\n", 182 + strerror(errno)); 183 + goto out; 184 + } 185 + if (read(fd, nr_hugepages, sizeof(nr_hugepages)) <= 0) { 186 + ksft_print_msg("Failed to read from /proc/sys/vm/nr_hugepages: %s\n", 187 + strerror(errno)); 188 + goto close_fd; 189 + } 190 + 191 + lseek(fd, 0, SEEK_SET); 192 + 193 + /* Start with the initial condition of 0 huge pages */ 194 + if (write(fd, "0", sizeof(char)) != sizeof(char)) { 195 + ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n", 196 + strerror(errno)); 197 + goto close_fd; 198 + } 199 + 200 + *initial_nr_hugepages = strtoul(nr_hugepages, NULL, 10); 201 + ret = 0; 202 + 203 + close_fd: 204 + close(fd); 205 + 206 + out: 207 + return ret; 208 + } 160 209 161 210 int main(int argc, char **argv) 162 211 { ··· 202 181 unsigned long mem_free = 0; 203 182 unsigned long hugepage_size = 0; 204 183 long mem_fragmentable_MB = 0; 184 + unsigned long initial_nr_hugepages; 205 185 206 186 ksft_print_header(); 207 187 ··· 210 188 ksft_exit_skip("Prerequisites unsatisfied\n"); 211 189 212 190 ksft_set_plan(1); 191 + 192 + /* Start the test without hugepages reducing mem_free */ 193 + if (set_zero_hugepages(&initial_nr_hugepages)) 194 + ksft_exit_fail(); 213 195 214 196 lim.rlim_cur = RLIM_INFINITY; 215 197 lim.rlim_max = RLIM_INFINITY; ··· 258 232 entry = entry->next; 259 233 } 260 234 261 - if (check_compaction(mem_free, hugepage_size) == 0) 235 + if (check_compaction(mem_free, hugepage_size, 236 + initial_nr_hugepages) == 0) 262 237 ksft_exit_pass(); 263 238 264 239 ksft_exit_fail();