1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * KSM functional tests 4 * 5 * Copyright 2022, Red Hat, Inc. 6 * 7 * Author(s): David Hildenbrand <david@redhat.com> 8 */ 9 #define _GNU_SOURCE 10 #include <stdlib.h> 11 #include <string.h> 12 #include <stdbool.h> 13 #include <stdint.h> 14 #include <unistd.h> 15 #include <errno.h> 16 #include <fcntl.h> 17 #include <sys/mman.h> 18 #include <sys/syscall.h> 19 #include <sys/ioctl.h> 20 #include <linux/userfaultfd.h> 21 22 #include "../kselftest.h" 23 #include "vm_util.h" 24 25 #define KiB 1024u 26 #define MiB (1024 * KiB) 27 28 static int ksm_fd; 29 static int ksm_full_scans_fd; 30 static int pagemap_fd; 31 static size_t pagesize; 32 33 static bool range_maps_duplicates(char *addr, unsigned long size) 34 { 35 unsigned long offs_a, offs_b, pfn_a, pfn_b; 36 37 /* 38 * There is no easy way to check if there are KSM pages mapped into 39 * this range. We only check that the range does not map the same PFN 40 * twice by comparing each pair of mapped pages. 41 */ 42 for (offs_a = 0; offs_a < size; offs_a += pagesize) { 43 pfn_a = pagemap_get_pfn(pagemap_fd, addr + offs_a); 44 /* Page not present or PFN not exposed by the kernel. */ 45 if (pfn_a == -1ul || !pfn_a) 46 continue; 47 48 for (offs_b = offs_a + pagesize; offs_b < size; 49 offs_b += pagesize) { 50 pfn_b = pagemap_get_pfn(pagemap_fd, addr + offs_b); 51 if (pfn_b == -1ul || !pfn_b) 52 continue; 53 if (pfn_a == pfn_b) 54 return true; 55 } 56 } 57 return false; 58 } 59 60 static long ksm_get_full_scans(void) 61 { 62 char buf[10]; 63 ssize_t ret; 64 65 ret = pread(ksm_full_scans_fd, buf, sizeof(buf) - 1, 0); 66 if (ret <= 0) 67 return -errno; 68 buf[ret] = 0; 69 70 return strtol(buf, NULL, 10); 71 } 72 73 static int ksm_merge(void) 74 { 75 long start_scans, end_scans; 76 77 /* Wait for two full scans such that any possible merging happened. */ 78 start_scans = ksm_get_full_scans(); 79 if (start_scans < 0) 80 return start_scans; 81 if (write(ksm_fd, "1", 1) != 1) 82 return -errno; 83 do { 84 end_scans = ksm_get_full_scans(); 85 if (end_scans < 0) 86 return end_scans; 87 } while (end_scans < start_scans + 2); 88 89 return 0; 90 } 91 92 static char *mmap_and_merge_range(char val, unsigned long size) 93 { 94 char *map; 95 96 map = mmap(NULL, size, PROT_READ|PROT_WRITE, 97 MAP_PRIVATE|MAP_ANON, -1, 0); 98 if (map == MAP_FAILED) { 99 ksft_test_result_fail("mmap() failed\n"); 100 return MAP_FAILED; 101 } 102 103 /* Don't use THP. Ignore if THP are not around on a kernel. */ 104 if (madvise(map, size, MADV_NOHUGEPAGE) && errno != EINVAL) { 105 ksft_test_result_fail("MADV_NOHUGEPAGE failed\n"); 106 goto unmap; 107 } 108 109 /* Make sure each page contains the same values to merge them. */ 110 memset(map, val, size); 111 if (madvise(map, size, MADV_MERGEABLE)) { 112 ksft_test_result_fail("MADV_MERGEABLE failed\n"); 113 goto unmap; 114 } 115 116 /* Run KSM to trigger merging and wait. */ 117 if (ksm_merge()) { 118 ksft_test_result_fail("Running KSM failed\n"); 119 goto unmap; 120 } 121 return map; 122 unmap: 123 munmap(map, size); 124 return MAP_FAILED; 125 } 126 127 static void test_unmerge(void) 128 { 129 const unsigned int size = 2 * MiB; 130 char *map; 131 132 ksft_print_msg("[RUN] %s\n", __func__); 133 134 map = mmap_and_merge_range(0xcf, size); 135 if (map == MAP_FAILED) 136 return; 137 138 if (madvise(map, size, MADV_UNMERGEABLE)) { 139 ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 140 goto unmap; 141 } 142 143 ksft_test_result(!range_maps_duplicates(map, size), 144 "Pages were unmerged\n"); 145 unmap: 146 munmap(map, size); 147 } 148 149 static void test_unmerge_discarded(void) 150 { 151 const unsigned int size = 2 * MiB; 152 char *map; 153 154 ksft_print_msg("[RUN] %s\n", __func__); 155 156 map = mmap_and_merge_range(0xcf, size); 157 if (map == MAP_FAILED) 158 return; 159 160 /* Discard half of all mapped pages so we have pte_none() entries. */ 161 if (madvise(map, size / 2, MADV_DONTNEED)) { 162 ksft_test_result_fail("MADV_DONTNEED failed\n"); 163 goto unmap; 164 } 165 166 if (madvise(map, size, MADV_UNMERGEABLE)) { 167 ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 168 goto unmap; 169 } 170 171 ksft_test_result(!range_maps_duplicates(map, size), 172 "Pages were unmerged\n"); 173 unmap: 174 munmap(map, size); 175 } 176 177 #ifdef __NR_userfaultfd 178 static void test_unmerge_uffd_wp(void) 179 { 180 struct uffdio_writeprotect uffd_writeprotect; 181 const unsigned int size = 2 * MiB; 182 struct uffdio_api uffdio_api; 183 char *map; 184 int uffd; 185 186 ksft_print_msg("[RUN] %s\n", __func__); 187 188 map = mmap_and_merge_range(0xcf, size); 189 if (map == MAP_FAILED) 190 return; 191 192 /* See if UFFD is around. */ 193 uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); 194 if (uffd < 0) { 195 ksft_test_result_skip("__NR_userfaultfd failed\n"); 196 goto unmap; 197 } 198 199 /* See if UFFD-WP is around. */ 200 uffdio_api.api = UFFD_API; 201 uffdio_api.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP; 202 if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) { 203 ksft_test_result_fail("UFFDIO_API failed\n"); 204 goto close_uffd; 205 } 206 if (!(uffdio_api.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP)) { 207 ksft_test_result_skip("UFFD_FEATURE_PAGEFAULT_FLAG_WP not available\n"); 208 goto close_uffd; 209 } 210 211 /* Register UFFD-WP, no need for an actual handler. */ 212 if (uffd_register(uffd, map, size, false, true, false)) { 213 ksft_test_result_fail("UFFDIO_REGISTER_MODE_WP failed\n"); 214 goto close_uffd; 215 } 216 217 /* Write-protect the range using UFFD-WP. */ 218 uffd_writeprotect.range.start = (unsigned long) map; 219 uffd_writeprotect.range.len = size; 220 uffd_writeprotect.mode = UFFDIO_WRITEPROTECT_MODE_WP; 221 if (ioctl(uffd, UFFDIO_WRITEPROTECT, &uffd_writeprotect)) { 222 ksft_test_result_fail("UFFDIO_WRITEPROTECT failed\n"); 223 goto close_uffd; 224 } 225 226 if (madvise(map, size, MADV_UNMERGEABLE)) { 227 ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 228 goto close_uffd; 229 } 230 231 ksft_test_result(!range_maps_duplicates(map, size), 232 "Pages were unmerged\n"); 233 close_uffd: 234 close(uffd); 235 unmap: 236 munmap(map, size); 237 } 238 #endif 239 240 int main(int argc, char **argv) 241 { 242 unsigned int tests = 2; 243 int err; 244 245 #ifdef __NR_userfaultfd 246 tests++; 247 #endif 248 249 ksft_print_header(); 250 ksft_set_plan(tests); 251 252 pagesize = getpagesize(); 253 254 ksm_fd = open("/sys/kernel/mm/ksm/run", O_RDWR); 255 if (ksm_fd < 0) 256 ksft_exit_skip("open(\"/sys/kernel/mm/ksm/run\") failed\n"); 257 ksm_full_scans_fd = open("/sys/kernel/mm/ksm/full_scans", O_RDONLY); 258 if (ksm_full_scans_fd < 0) 259 ksft_exit_skip("open(\"/sys/kernel/mm/ksm/full_scans\") failed\n"); 260 pagemap_fd = open("/proc/self/pagemap", O_RDONLY); 261 if (pagemap_fd < 0) 262 ksft_exit_skip("open(\"/proc/self/pagemap\") failed\n"); 263 264 test_unmerge(); 265 test_unmerge_discarded(); 266 #ifdef __NR_userfaultfd 267 test_unmerge_uffd_wp(); 268 #endif 269 270 err = ksft_get_fail_cnt(); 271 if (err) 272 ksft_exit_fail_msg("%d out of %d tests failed\n", 273 err, ksft_test_num()); 274 return ksft_exit_pass(); 275 } 276