| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- // SPDX-License-Identifier: GPL-2.0
- #include <stdio.h>
- #include <string.h>
- #include <stdbool.h>
- #include <fcntl.h>
- #include <stdint.h>
- #include <malloc.h>
- #include <sys/mman.h>
- #include "kselftest.h"
- #include "vm_util.h"
- #include "thp_settings.h"
- #define PAGEMAP_FILE_PATH "/proc/self/pagemap"
- #define TEST_ITERATIONS 10000
- static void test_simple(int pagemap_fd, int pagesize)
- {
- int i;
- char *map;
- map = aligned_alloc(pagesize, pagesize);
- if (!map)
- ksft_exit_fail_msg("mmap failed\n");
- clear_softdirty();
- for (i = 0 ; i < TEST_ITERATIONS; i++) {
- if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
- ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
- break;
- }
- clear_softdirty();
- // Write something to the page to get the dirty bit enabled on the page
- map[0]++;
- if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
- ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
- break;
- }
- clear_softdirty();
- }
- free(map);
- ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
- }
- static void test_vma_reuse(int pagemap_fd, int pagesize)
- {
- char *map, *map2;
- map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
- if (map == MAP_FAILED)
- ksft_exit_fail_msg("mmap failed");
- // The kernel always marks new regions as soft dirty
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
- "Test %s dirty bit of allocated page\n", __func__);
- clear_softdirty();
- munmap(map, pagesize);
- map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
- if (map2 == MAP_FAILED)
- ksft_exit_fail_msg("mmap failed");
- // Dirty bit is set for new regions even if they are reused
- if (map == map2)
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
- "Test %s dirty bit of reused address page\n", __func__);
- else
- ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__);
- munmap(map2, pagesize);
- }
- static void test_hugepage(int pagemap_fd, int pagesize)
- {
- char *map;
- int i, ret;
- if (!thp_is_enabled()) {
- ksft_test_result_skip("Transparent Hugepages not available\n");
- return;
- }
- size_t hpage_len = read_pmd_pagesize();
- if (!hpage_len)
- ksft_exit_fail_msg("Reading PMD pagesize failed");
- map = memalign(hpage_len, hpage_len);
- if (!map)
- ksft_exit_fail_msg("memalign failed\n");
- ret = madvise(map, hpage_len, MADV_HUGEPAGE);
- if (ret)
- ksft_exit_fail_msg("madvise failed %d\n", ret);
- for (i = 0; i < hpage_len; i++)
- map[i] = (char)i;
- if (check_huge_anon(map, 1, hpage_len)) {
- ksft_test_result_pass("Test %s huge page allocation\n", __func__);
- clear_softdirty();
- for (i = 0 ; i < TEST_ITERATIONS ; i++) {
- if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
- ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
- break;
- }
- clear_softdirty();
- // Write something to the page to get the dirty bit enabled on the page
- map[0]++;
- if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
- ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
- break;
- }
- clear_softdirty();
- }
- ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__);
- } else {
- // hugepage allocation failed. skip these tests
- ksft_test_result_skip("Test %s huge page allocation\n", __func__);
- ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
- }
- free(map);
- }
- static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
- {
- const char *type[] = {"file", "anon"};
- const char *fname = "./soft-dirty-test-file";
- int test_fd = 0;
- char *map;
- if (anon) {
- map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
- MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
- if (!map)
- ksft_exit_fail_msg("anon mmap failed\n");
- } else {
- test_fd = open(fname, O_RDWR | O_CREAT, 0664);
- if (test_fd < 0) {
- ksft_test_result_skip("Test %s open() file failed\n", __func__);
- return;
- }
- unlink(fname);
- ftruncate(test_fd, pagesize);
- map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
- MAP_SHARED, test_fd, 0);
- if (!map)
- ksft_exit_fail_msg("file mmap failed\n");
- }
- *map = 1;
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
- "Test %s-%s dirty bit of new written page\n",
- __func__, type[anon]);
- clear_softdirty();
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
- "Test %s-%s soft-dirty clear after clear_refs\n",
- __func__, type[anon]);
- mprotect(map, pagesize, PROT_READ);
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
- "Test %s-%s soft-dirty clear after marking RO\n",
- __func__, type[anon]);
- mprotect(map, pagesize, PROT_READ|PROT_WRITE);
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
- "Test %s-%s soft-dirty clear after marking RW\n",
- __func__, type[anon]);
- *map = 2;
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
- "Test %s-%s soft-dirty after rewritten\n",
- __func__, type[anon]);
- munmap(map, pagesize);
- if (!anon)
- close(test_fd);
- }
- static void test_merge(int pagemap_fd, int pagesize)
- {
- char *reserved, *map, *map2;
- /*
- * Reserve space for tests:
- *
- * ---padding to ---
- * | avoid adj. |
- * v merge v
- * |---|---|---|---|---|
- * | | 1 | 2 | 3 | |
- * |---|---|---|---|---|
- */
- reserved = mmap(NULL, 5 * pagesize, PROT_NONE,
- MAP_ANON | MAP_PRIVATE, -1, 0);
- if (reserved == MAP_FAILED)
- ksft_exit_fail_msg("mmap failed\n");
- munmap(reserved, 4 * pagesize);
- /*
- * Establish initial VMA:
- *
- * S/D
- * |---|---|---|---|---|
- * | | 1 | | | |
- * |---|---|---|---|---|
- */
- map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
- MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
- if (map == MAP_FAILED)
- ksft_exit_fail_msg("mmap failed\n");
- /* This will clear VM_SOFTDIRTY too. */
- clear_softdirty();
- /*
- * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from
- * map:
- *
- * - S/D
- * |---|---|---|---|---|
- * | | 1 | | 2 | |
- * |---|---|---|---|---|
- */
- map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE,
- MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
- if (map2 == MAP_FAILED)
- ksft_exit_fail_msg("mmap failed\n");
- /*
- * Now remap it immediately adjacent to map, if the merge correctly
- * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole
- * being marked soft-dirty:
- *
- * merge
- * S/D
- * |---|-------|---|---|
- * | | 1 | | |
- * |---|-------|---|---|
- */
- map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE,
- &reserved[2 * pagesize]);
- if (map2 == MAP_FAILED)
- ksft_exit_fail_msg("mremap failed\n");
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
- "Test %s-anon soft-dirty after remap merge 1st pg\n",
- __func__);
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
- "Test %s-anon soft-dirty after remap merge 2nd pg\n",
- __func__);
- munmap(map, 2 * pagesize);
- /*
- * Now establish another VMA:
- *
- * S/D
- * |---|---|---|---|---|
- * | | 1 | | | |
- * |---|---|---|---|---|
- */
- map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
- MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
- if (map == MAP_FAILED)
- ksft_exit_fail_msg("mmap failed\n");
- /* Clear VM_SOFTDIRTY... */
- clear_softdirty();
- /* ...and establish incompatible adjacent VMA:
- *
- * - S/D
- * |---|---|---|---|---|
- * | | 1 | 2 | | |
- * |---|---|---|---|---|
- */
- map2 = mmap(&reserved[2 * pagesize], pagesize,
- PROT_READ | PROT_WRITE | PROT_EXEC,
- MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
- if (map2 == MAP_FAILED)
- ksft_exit_fail_msg("mmap failed\n");
- /*
- * Now mprotect() VMA 1 so it's compatible with 2 and therefore merges:
- *
- * merge
- * S/D
- * |---|-------|---|---|
- * | | 1 | | |
- * |---|-------|---|---|
- */
- if (mprotect(map, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC))
- ksft_exit_fail_msg("mprotect failed\n");
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
- "Test %s-anon soft-dirty after mprotect merge 1st pg\n",
- __func__);
- ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
- "Test %s-anon soft-dirty after mprotect merge 2nd pg\n",
- __func__);
- munmap(map, 2 * pagesize);
- }
- static void test_mprotect_anon(int pagemap_fd, int pagesize)
- {
- test_mprotect(pagemap_fd, pagesize, true);
- }
- static void test_mprotect_file(int pagemap_fd, int pagesize)
- {
- test_mprotect(pagemap_fd, pagesize, false);
- }
- int main(int argc, char **argv)
- {
- int pagemap_fd;
- int pagesize;
- ksft_print_header();
- if (!softdirty_supported())
- ksft_exit_skip("soft-dirty is not support\n");
- ksft_set_plan(19);
- pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
- if (pagemap_fd < 0)
- ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
- pagesize = getpagesize();
- test_simple(pagemap_fd, pagesize);
- test_vma_reuse(pagemap_fd, pagesize);
- test_hugepage(pagemap_fd, pagesize);
- test_mprotect_anon(pagemap_fd, pagesize);
- test_mprotect_file(pagemap_fd, pagesize);
- test_merge(pagemap_fd, pagesize);
- close(pagemap_fd);
- ksft_finished();
- }
|