Both hints are non-destructive, but the tests so far only check that for private anonymous memory. Shared memory reaches reclaim through different paths: a dirty shared file page must be written back or left resident, shared anonymous memory is paged out through shmem, and a SHM_LOCK'd segment is unevictable without its VMA being VM_LOCKED.
Check that MADV_COLD and MADV_PAGEOUT preserve a dirty shared file page, that MADV_PAGEOUT preserves a shared anonymous page, and that it leaves a SHM_LOCK'd page resident. The SHM_LOCK test needs root to confirm through /proc/kpageflags that the page is unevictable, and skips if it is not. Signed-off-by: Gregory Price (Meta) <[email protected]> --- tools/testing/selftests/mm/madvise.c | 138 +++++++++++++++++++++++++++ tools/testing/selftests/mm/vm_util.h | 1 + 2 files changed, 139 insertions(+) diff --git a/tools/testing/selftests/mm/madvise.c b/tools/testing/selftests/mm/madvise.c index bd78f63f1314..0851f5428e3d 100644 --- a/tools/testing/selftests/mm/madvise.c +++ b/tools/testing/selftests/mm/madvise.c @@ -7,7 +7,10 @@ #include <fcntl.h> #include <sched.h> #include <stdbool.h> +#include <stdint.h> +#include <sys/ipc.h> #include <sys/mman.h> +#include <sys/shm.h> #include <unistd.h> #include "kselftest_harness.h" @@ -21,6 +24,8 @@ static size_t page_size; FIXTURE(madvise) { int pagemap_fd; + /* Opening it needs CAP_SYS_ADMIN, -1 otherwise. */ + int kpageflags_fd; }; FIXTURE_SETUP(madvise) @@ -29,10 +34,13 @@ FIXTURE_SETUP(madvise) self->pagemap_fd = open("/proc/self/pagemap", O_RDONLY); ASSERT_GE(self->pagemap_fd, 0); + self->kpageflags_fd = open("/proc/kpageflags", O_RDONLY); } FIXTURE_TEARDOWN(madvise) { + if (self->kpageflags_fd >= 0) + close(self->kpageflags_fd); close(self->pagemap_fd); } @@ -52,6 +60,22 @@ static bool pageout_until_swapped(struct __test_metadata *_metadata, return false; } +/* Page out the range until its first page is no longer mapped. */ +static bool pageout_until_evicted(struct __test_metadata *_metadata, + char *mapping, size_t size, int pagemap_fd) +{ + int retry; + + for (retry = 0; retry < PAGEOUT_RETRIES; retry++) { + ASSERT_EQ(madvise(mapping, size, MADV_PAGEOUT), 0); + if (!pagemap_is_populated(pagemap_fd, mapping)) + return true; + usleep(10000); + } + + return false; +} + /* * Keep this task on one CPU, so that draining that CPU's LRU batches drains * the folios this task faulted in. @@ -67,6 +91,25 @@ static void pin_to_current_cpu(struct __test_metadata *_metadata) ASSERT_EQ(sched_setaffinity(0, sizeof(mask), &mask), 0); } +/* Map a shared mapping of an unlinked, zero-filled temporary file. */ +static char *map_temp_file(struct __test_metadata *_metadata, size_t size) +{ + char template[] = "/tmp/madvise-pageout-XXXXXX"; + char *mapping; + int fd; + + fd = mkstemp(template); + ASSERT_GE(fd, 0); + ASSERT_EQ(unlink(template), 0); + ASSERT_EQ(ftruncate(fd, size), 0); + + mapping = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ASSERT_NE(mapping, MAP_FAILED); + close(fd); + + return mapping; +} + /* * 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 @@ -267,4 +310,99 @@ TEST_F(madvise, pageout_skips_swapped_pte) check_skips_swapped_pte(_metadata, self->pagemap_fd, MADV_PAGEOUT); } +/* MADV_COLD must not discard a dirty page of a shared file mapping. */ +TEST_F(madvise, cold_preserves_dirty_shared_file_page) +{ + char *mapping; + + mapping = map_temp_file(_metadata, page_size); + mapping[0] = 1; + + ASSERT_EQ(madvise(mapping, page_size, MADV_COLD), 0); + ASSERT_EQ(mapping[0], 1); + + ASSERT_EQ(munmap(mapping, page_size), 0); +} + +/* + * MADV_PAGEOUT may write a dirty shared file page back or leave it resident, + * but must not discard it. + */ +TEST_F(madvise, pageout_preserves_dirty_shared_file_page) +{ + char *mapping; + + mapping = map_temp_file(_metadata, page_size); + mapping[0] = 1; + + if (!pageout_until_evicted(_metadata, mapping, page_size, + self->pagemap_fd)) + SKIP(return, "MADV_PAGEOUT did not unmap the page"); + ASSERT_EQ(mapping[0], 1); + + ASSERT_EQ(munmap(mapping, page_size), 0); +} + +/* MADV_PAGEOUT must not lose the contents of shared anonymous memory. */ +TEST_F(madvise, pageout_preserves_shared_anon_page) +{ + char *mapping; + + mapping = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(mapping, MAP_FAILED); + mapping[0] = 1; + + if (!pageout_until_evicted(_metadata, mapping, page_size, + self->pagemap_fd)) + SKIP(return, "MADV_PAGEOUT did not evict the page"); + ASSERT_EQ(mapping[0], 1); + + ASSERT_EQ(munmap(mapping, page_size), 0); +} + +/* + * SHM_LOCK makes shmem pages unevictable without setting VM_LOCKED on the + * VMA, so MADV_PAGEOUT accepts the range but must not reclaim the page. + */ +TEST_F(madvise, pageout_skips_unevictable_page) +{ + unsigned long pfn; + uint64_t flags; + char *mapping; + int shmid; + + if (self->kpageflags_fd < 0) + SKIP(return, "requires root to read page flags"); + shmid = shmget(IPC_PRIVATE, page_size, IPC_CREAT | 0600); + ASSERT_GE(shmid, 0); + if (shmctl(shmid, SHM_LOCK, NULL)) { + shmctl(shmid, IPC_RMID, NULL); + SKIP(return, "could not lock a shmem segment"); + } + mapping = shmat(shmid, NULL, 0); + /* The segment is destroyed once it is detached. */ + shmctl(shmid, IPC_RMID, NULL); + ASSERT_NE(mapping, (void *)-1); + pin_to_current_cpu(_metadata); + mapping[0] = 1; + + /* + * MADV_COLD drains this CPU's LRU add batch, which makes the page + * unevictable. + */ + ASSERT_EQ(madvise(mapping, page_size, MADV_COLD), 0); + pfn = pagemap_get_pfn(self->pagemap_fd, mapping); + ASSERT_NE(pfn, -1UL); + ASSERT_EQ(pageflags_get(pfn, self->kpageflags_fd, &flags), 0); + if (!(flags & KPF_UNEVICTABLE)) + SKIP(return, "could not make the page unevictable"); + + ASSERT_EQ(madvise(mapping, page_size, MADV_PAGEOUT), 0); + ASSERT_TRUE(pagemap_is_populated(self->pagemap_fd, mapping)); + ASSERT_EQ(mapping[0], 1); + + ASSERT_EQ(shmdt(mapping), 0); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h index b5d59729d432..95898e045bac 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -24,6 +24,7 @@ #define KPF_COMPOUND_HEAD BIT_ULL(15) #define KPF_COMPOUND_TAIL BIT_ULL(16) +#define KPF_UNEVICTABLE BIT_ULL(18) #define KPF_HWPOISON BIT_ULL(19) #define KPF_THP BIT_ULL(22) /* -- 2.56.0

