| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * RMAP functional tests
- *
- * Author(s): Wei Yang <richard.weiyang@gmail.com>
- */
- #include "kselftest_harness.h"
- #include <strings.h>
- #include <pthread.h>
- #include <numa.h>
- #include <numaif.h>
- #include <sys/mman.h>
- #include <sys/prctl.h>
- #include <sys/types.h>
- #include <signal.h>
- #include <time.h>
- #include <sys/sem.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include "vm_util.h"
- #define TOTAL_LEVEL 5
- #define MAX_CHILDREN 3
- #define FAIL_ON_CHECK (1 << 0)
- #define FAIL_ON_WORK (1 << 1)
- struct sembuf sem_wait = {0, -1, 0};
- struct sembuf sem_signal = {0, 1, 0};
- enum backend_type {
- ANON,
- SHM,
- NORM_FILE,
- };
- #define PREFIX "kst_rmap"
- #define MAX_FILENAME_LEN 256
- const char *suffixes[] = {
- "",
- "_shm",
- "_file",
- };
- struct global_data;
- typedef int (*work_fn)(struct global_data *data);
- typedef int (*check_fn)(struct global_data *data);
- typedef void (*prepare_fn)(struct global_data *data);
- struct global_data {
- int worker_level;
- int semid;
- int pipefd[2];
- unsigned int mapsize;
- unsigned int rand_seed;
- char *region;
- prepare_fn do_prepare;
- work_fn do_work;
- check_fn do_check;
- enum backend_type backend;
- char filename[MAX_FILENAME_LEN];
- unsigned long *expected_pfn;
- };
- /*
- * Create a process tree with TOTAL_LEVEL height and at most MAX_CHILDREN
- * children for each.
- *
- * It will randomly select one process as 'worker' process which will
- * 'do_work' until all processes are created. And all other processes will
- * wait until 'worker' finish its work.
- */
- void propagate_children(struct __test_metadata *_metadata, struct global_data *data)
- {
- pid_t root_pid, pid;
- unsigned int num_child;
- int status;
- int ret = 0;
- int curr_child, worker_child;
- int curr_level = 1;
- bool is_worker = true;
- root_pid = getpid();
- repeat:
- num_child = rand_r(&data->rand_seed) % MAX_CHILDREN + 1;
- worker_child = is_worker ? rand_r(&data->rand_seed) % num_child : -1;
- for (curr_child = 0; curr_child < num_child; curr_child++) {
- pid = fork();
- if (pid < 0) {
- perror("Error: fork\n");
- } else if (pid == 0) {
- curr_level++;
- if (curr_child != worker_child)
- is_worker = false;
- if (curr_level == TOTAL_LEVEL)
- break;
- data->rand_seed += curr_child;
- goto repeat;
- }
- }
- if (data->do_prepare)
- data->do_prepare(data);
- close(data->pipefd[1]);
- if (is_worker && curr_level == data->worker_level) {
- /* This is the worker process, first wait last process created */
- char buf;
- while (read(data->pipefd[0], &buf, 1) > 0)
- ;
- if (data->do_work)
- ret = data->do_work(data);
- /* Kick others */
- semctl(data->semid, 0, IPC_RMID);
- } else {
- /* Wait worker finish */
- semop(data->semid, &sem_wait, 1);
- if (data->do_check)
- ret = data->do_check(data);
- }
- /* Wait all child to quit */
- while (wait(&status) > 0) {
- if (WIFEXITED(status))
- ret |= WEXITSTATUS(status);
- }
- if (getpid() == root_pid) {
- if (ret & FAIL_ON_WORK)
- SKIP(return, "Failed in worker");
- ASSERT_EQ(ret, 0);
- } else {
- exit(ret);
- }
- }
- FIXTURE(migrate)
- {
- struct global_data data;
- };
- FIXTURE_SETUP(migrate)
- {
- struct global_data *data = &self->data;
- if (numa_available() < 0)
- SKIP(return, "NUMA not available");
- if (numa_bitmask_weight(numa_all_nodes_ptr) <= 1)
- SKIP(return, "Not enough NUMA nodes available");
- data->mapsize = getpagesize();
- data->expected_pfn = mmap(0, sizeof(unsigned long),
- PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- ASSERT_NE(data->expected_pfn, MAP_FAILED);
- /* Prepare semaphore */
- data->semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
- ASSERT_NE(data->semid, -1);
- ASSERT_NE(semctl(data->semid, 0, SETVAL, 0), -1);
- /* Prepare pipe */
- ASSERT_NE(pipe(data->pipefd), -1);
- data->rand_seed = time(NULL);
- srand(data->rand_seed);
- data->worker_level = rand() % TOTAL_LEVEL + 1;
- data->do_prepare = NULL;
- data->do_work = NULL;
- data->do_check = NULL;
- data->backend = ANON;
- };
- FIXTURE_TEARDOWN(migrate)
- {
- struct global_data *data = &self->data;
- if (data->region != MAP_FAILED)
- munmap(data->region, data->mapsize);
- data->region = MAP_FAILED;
- if (data->expected_pfn != MAP_FAILED)
- munmap(data->expected_pfn, sizeof(unsigned long));
- data->expected_pfn = MAP_FAILED;
- semctl(data->semid, 0, IPC_RMID);
- data->semid = -1;
- close(data->pipefd[0]);
- switch (data->backend) {
- case ANON:
- break;
- case SHM:
- shm_unlink(data->filename);
- break;
- case NORM_FILE:
- unlink(data->filename);
- break;
- }
- }
- void access_region(struct global_data *data)
- {
- /*
- * Force read "region" to make sure page fault in.
- */
- FORCE_READ(*data->region);
- }
- int try_to_move_page(char *region)
- {
- int ret;
- int node;
- int status = 0;
- int failures = 0;
- ret = move_pages(0, 1, (void **)®ion, NULL, &status, MPOL_MF_MOVE_ALL);
- if (ret != 0) {
- perror("Failed to get original numa");
- return FAIL_ON_WORK;
- }
- /* Pick up a different target node */
- for (node = 0; node <= numa_max_node(); node++) {
- if (numa_bitmask_isbitset(numa_all_nodes_ptr, node) && node != status)
- break;
- }
- if (node > numa_max_node()) {
- ksft_print_msg("Couldn't find available numa node for testing\n");
- return FAIL_ON_WORK;
- }
- while (1) {
- ret = move_pages(0, 1, (void **)®ion, &node, &status, MPOL_MF_MOVE_ALL);
- /* migrate successfully */
- if (!ret)
- break;
- /* error happened */
- if (ret < 0) {
- ksft_perror("Failed to move pages");
- return FAIL_ON_WORK;
- }
- /* migration is best effort; try again */
- if (++failures >= 100)
- return FAIL_ON_WORK;
- }
- return 0;
- }
- int move_region(struct global_data *data)
- {
- int ret;
- int pagemap_fd;
- ret = try_to_move_page(data->region);
- if (ret != 0)
- return ret;
- pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
- if (pagemap_fd == -1)
- return FAIL_ON_WORK;
- *data->expected_pfn = pagemap_get_pfn(pagemap_fd, data->region);
- return 0;
- }
- int has_same_pfn(struct global_data *data)
- {
- unsigned long pfn;
- int pagemap_fd;
- if (data->region == MAP_FAILED)
- return 0;
- pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
- if (pagemap_fd == -1)
- return FAIL_ON_CHECK;
- pfn = pagemap_get_pfn(pagemap_fd, data->region);
- if (pfn != *data->expected_pfn)
- return FAIL_ON_CHECK;
- return 0;
- }
- TEST_F(migrate, anon)
- {
- struct global_data *data = &self->data;
- /* Map an area and fault in */
- data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- ASSERT_NE(data->region, MAP_FAILED);
- memset(data->region, 0xcf, data->mapsize);
- data->do_prepare = access_region;
- data->do_work = move_region;
- data->do_check = has_same_pfn;
- propagate_children(_metadata, data);
- }
- TEST_F(migrate, shm)
- {
- int shm_fd;
- struct global_data *data = &self->data;
- snprintf(data->filename, MAX_FILENAME_LEN, "%s%s", PREFIX, suffixes[SHM]);
- shm_fd = shm_open(data->filename, O_CREAT | O_RDWR, 0666);
- ASSERT_NE(shm_fd, -1);
- ftruncate(shm_fd, data->mapsize);
- data->backend = SHM;
- /* Map a shared area and fault in */
- data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
- MAP_SHARED, shm_fd, 0);
- ASSERT_NE(data->region, MAP_FAILED);
- memset(data->region, 0xcf, data->mapsize);
- close(shm_fd);
- data->do_prepare = access_region;
- data->do_work = move_region;
- data->do_check = has_same_pfn;
- propagate_children(_metadata, data);
- }
- TEST_F(migrate, file)
- {
- int fd;
- struct global_data *data = &self->data;
- snprintf(data->filename, MAX_FILENAME_LEN, "%s%s", PREFIX, suffixes[NORM_FILE]);
- fd = open(data->filename, O_CREAT | O_RDWR | O_EXCL, 0666);
- ASSERT_NE(fd, -1);
- ftruncate(fd, data->mapsize);
- data->backend = NORM_FILE;
- /* Map a shared area and fault in */
- data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
- MAP_SHARED, fd, 0);
- ASSERT_NE(data->region, MAP_FAILED);
- memset(data->region, 0xcf, data->mapsize);
- close(fd);
- data->do_prepare = access_region;
- data->do_work = move_region;
- data->do_check = has_same_pfn;
- propagate_children(_metadata, data);
- }
- void prepare_local_region(struct global_data *data)
- {
- /* Allocate range and set the same data */
- data->region = mmap(NULL, data->mapsize, PROT_READ|PROT_WRITE,
- MAP_PRIVATE|MAP_ANON, -1, 0);
- if (data->region == MAP_FAILED)
- return;
- memset(data->region, 0xcf, data->mapsize);
- }
- int merge_and_migrate(struct global_data *data)
- {
- int pagemap_fd;
- int ret = 0;
- if (data->region == MAP_FAILED)
- return FAIL_ON_WORK;
- if (ksm_start() < 0)
- return FAIL_ON_WORK;
- ret = try_to_move_page(data->region);
- pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
- if (pagemap_fd == -1)
- return FAIL_ON_WORK;
- *data->expected_pfn = pagemap_get_pfn(pagemap_fd, data->region);
- return ret;
- }
- TEST_F(migrate, ksm)
- {
- int ret;
- struct global_data *data = &self->data;
- if (ksm_stop() < 0)
- SKIP(return, "accessing \"/sys/kernel/mm/ksm/run\") failed");
- if (ksm_get_full_scans() < 0)
- SKIP(return, "accessing \"/sys/kernel/mm/ksm/full_scan\") failed");
- ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0);
- if (ret < 0 && errno == EINVAL)
- SKIP(return, "PR_SET_MEMORY_MERGE not supported");
- else if (ret)
- ksft_exit_fail_perror("PR_SET_MEMORY_MERGE=1 failed");
- data->do_prepare = prepare_local_region;
- data->do_work = merge_and_migrate;
- data->do_check = has_same_pfn;
- propagate_children(_metadata, data);
- }
- TEST_HARNESS_MAIN
|