madvise(2) accepts a zero-length range, rejects an unaligned start, and reports an unmapped gap in the range with ENOMEM while still applying the advice to the mapped pages on either side.
Check each of these for both hints. Around the gap, only MADV_PAGEOUT has an effect the test can observe, so only MADV_PAGEOUT is checked for paging out both neighbours, after a control page shows that swap works. Signed-off-by: Gregory Price (Meta) <[email protected]> --- tools/testing/selftests/mm/madvise.c | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/tools/testing/selftests/mm/madvise.c b/tools/testing/selftests/mm/madvise.c index 42cc08fe45ae..f122154fac05 100644 --- a/tools/testing/selftests/mm/madvise.c +++ b/tools/testing/selftests/mm/madvise.c @@ -5,6 +5,7 @@ #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> +#include <sched.h> #include <stdbool.h> #include <sys/mman.h> #include <unistd.h> @@ -51,6 +52,21 @@ static bool pageout_until_swapped(struct __test_metadata *_metadata, return false; } +/* + * Keep this task on one CPU, so that draining that CPU's LRU batches drains + * the folios this task faulted in. + */ +static void pin_to_current_cpu(struct __test_metadata *_metadata) +{ + int cpu = sched_getcpu(); + cpu_set_t mask; + + ASSERT_GE(cpu, 0); + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + ASSERT_EQ(sched_setaffinity(0, sizeof(mask), &mask), 0); +} + /* * Both hints are best effort, so these tests check what userspace can rely * on: which mappings are accepted, which pages must never be reclaimed, and @@ -111,6 +127,93 @@ TEST_F(madvise, locked_vma_is_rejected) ASSERT_EQ(munmap(mapping, page_size), 0); } +/* A zero-length range is accepted. */ +TEST_F(madvise, zero_length_is_accepted) +{ + char *mapping; + + mapping = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(mapping, MAP_FAILED); + + ASSERT_EQ(madvise(mapping, 0, MADV_COLD), 0); + ASSERT_EQ(madvise(mapping, 0, MADV_PAGEOUT), 0); + + ASSERT_EQ(munmap(mapping, page_size), 0); +} + +/* The start of the range must be page-aligned. */ +TEST_F(madvise, unaligned_start_is_rejected) +{ + char *mapping; + + mapping = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(mapping, MAP_FAILED); + mapping[0] = 1; + + ASSERT_EQ(madvise(mapping + 1, page_size, MADV_COLD), -1); + ASSERT_EQ(errno, EINVAL); + ASSERT_EQ(madvise(mapping + 1, page_size, MADV_PAGEOUT), -1); + ASSERT_EQ(errno, EINVAL); + ASSERT_EQ(mapping[0], 1); + + ASSERT_EQ(munmap(mapping, 2 * page_size), 0); +} + +/* + * An unmapped gap in the range makes either hint fail with ENOMEM, after + * advising the mapped pages on both sides of the gap. Only MADV_PAGEOUT has + * an effect on those pages that the test can observe. + */ +TEST_F(madvise, unmapped_gap_fails_after_advising) +{ + char *mapping, *control; + bool swapped = false; + int retry; + + pin_to_current_cpu(_metadata); + + /* Make sure MADV_PAGEOUT can swap at all before relying on it. */ + control = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(control, MAP_FAILED); + control[0] = 1; + if (!pageout_until_swapped(_metadata, control, page_size, + self->pagemap_fd)) + SKIP(return, "MADV_PAGEOUT did not swap the page"); + + mapping = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(mapping, MAP_FAILED); + mapping[0] = 1; + mapping[2 * page_size] = 1; + ASSERT_EQ(munmap(mapping + page_size, page_size), 0); + + ASSERT_EQ(madvise(mapping, 3 * page_size, MADV_COLD), -1); + ASSERT_EQ(errno, ENOMEM); + ASSERT_EQ(mapping[0], 1); + ASSERT_EQ(mapping[2 * page_size], 1); + + for (retry = 0; retry < PAGEOUT_RETRIES && !swapped; retry++) { + ASSERT_EQ(madvise(mapping, 3 * page_size, MADV_PAGEOUT), -1); + ASSERT_EQ(errno, ENOMEM); + swapped = pagemap_is_swapped(self->pagemap_fd, mapping) && + pagemap_is_swapped(self->pagemap_fd, + mapping + 2 * page_size); + if (!swapped) + usleep(10000); + } + ASSERT_TRUE(swapped) + TH_LOG("MADV_PAGEOUT did not page out both sides of the gap"); + ASSERT_EQ(mapping[0], 1); + ASSERT_EQ(mapping[2 * page_size], 1); + + ASSERT_EQ(munmap(mapping, page_size), 0); + ASSERT_EQ(munmap(mapping + 2 * page_size, page_size), 0); + ASSERT_EQ(munmap(control, page_size), 0); +} + /* Neither hint may fault a swapped page back in. */ static void check_skips_swapped_pte(struct __test_metadata *_metadata, int pagemap_fd, int advice) -- 2.56.0

