[CRIU] [PATCH v3 12/12] test: add test for anon shmem dedup
Eugene Batalov
eabatalov89 at gmail.com
Sun Aug 7 06:11:16 PDT 2016
Main test features:
- Non trivial ps tree with non trivial anon shmem regions
(no such test exists now).
- Each ps tree process continuously writes parts of anon shmem
vmas and validates these writes after restore
(required for dedup testing).
- Checking simultaneous changing of anon shmem contents in different
processes.
Signed-off-by: Eugene Batalov <eabatalov89 at gmail.com>
---
test/jenkins/criu-dedup.sh | 5 +-
test/zdtm/transition/Makefile | 1 +
test/zdtm/transition/maps008.c | 483 ++++++++++++++++++++++++++++++++++++++
test/zdtm/transition/maps008.desc | 1 +
4 files changed, 488 insertions(+), 2 deletions(-)
create mode 100644 test/zdtm/transition/maps008.c
create mode 100644 test/zdtm/transition/maps008.desc
diff --git a/test/jenkins/criu-dedup.sh b/test/jenkins/criu-dedup.sh
index 8705a95..a6dc7dc 100755
--- a/test/jenkins/criu-dedup.sh
+++ b/test/jenkins/criu-dedup.sh
@@ -4,8 +4,9 @@ source `dirname $0`/criu-lib.sh
prep
./test/zdtm.py run --all --keep-going --report report --parallel 4 -f h --pre 2 --dedup -x maps04 -x maps007 || fail
-# Additionally run these two as they touch a lot of
+# Additionally run these tests as they touch a lot of
# memory and it makes sense to additionally check it
-# with delays petween iterations
+# with delays between iterations
./test/zdtm.py run -t zdtm/transition/maps007 --keep-going --report report -f h --pre 8:.1 --dedup || fail
./test/zdtm.py run -t zdtm/static/mem-touch --keep-going --report report -f h --pre 8:.1 --dedup || fail
+./test/zdtm.py run -t zdtm/transition/maps008 --keep-going --report report -f h --pre 8:.1 --dedup || fail
diff --git a/test/zdtm/transition/Makefile b/test/zdtm/transition/Makefile
index 99ed19d..f3797ad 100644
--- a/test/zdtm/transition/Makefile
+++ b/test/zdtm/transition/Makefile
@@ -15,6 +15,7 @@ TST_NOFILE = \
fork2 \
thread-bomb \
maps007 \
+ maps008 \
pipe_loop00 \
pipe_shared00 \
socket_loop00 \
diff --git a/test/zdtm/transition/maps008.c b/test/zdtm/transition/maps008.c
new file mode 100644
index 0000000..fad5f10
--- /dev/null
+++ b/test/zdtm/transition/maps008.c
@@ -0,0 +1,483 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <linux/limits.h>
+#include "zdtmtst.h"
+
+const char *test_doc = "ps tree with anon shared vmas for dedup";
+
+/*
+ * 1. ps tree with non triavial anon shmem vmas is created first.
+ * 2. Each process gets its portion of shmem vmas.
+ * 3. Each process continiously datagens its portion until
+ * criu dump is finished.
+ * 4. Each process datachecks all its shmem portions after restore.
+ * 5. Contents of anon shmem vmas are checked for equality in
+ * different processes.
+ */
+
+typedef int (*proc_func_t)(task_waiter_t *setup_waiter);
+
+static pid_t fork_and_setup(proc_func_t pfunc)
+{
+ task_waiter_t setup_waiter;
+ pid_t pid;
+
+ task_waiter_init(&setup_waiter);
+ pid = test_fork();
+ if (pid < 0) {
+ pr_perror("fork failed");
+ exit(1);
+ }
+
+ if (pid == 0)
+ exit(pfunc(&setup_waiter));
+
+ task_waiter_wait4(&setup_waiter, pid);
+ task_waiter_fini(&setup_waiter);
+ return pid;
+}
+
+static void cont_and_wait_child(pid_t pid)
+{
+ int status;
+
+ kill(pid, SIGTERM);
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status))
+ exit(WEXITSTATUS(status));
+ } else
+ exit(1);
+}
+
+static void *mmap_ashmem(size_t size)
+{
+ void *mem = mmap(NULL, size, PROT_WRITE | PROT_READ,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (mem == MAP_FAILED) {
+ pr_perror("Can't map shmem %zx", size);
+ exit(1);
+ }
+ return mem;
+}
+
+static void *mmap_proc_mem(pid_t pid, unsigned long addr,
+ unsigned long size)
+{
+ int fd;
+ void *mem;
+ char path[PATH_MAX];
+
+ snprintf(path, PATH_MAX, "/proc/%d/map_files/%lx-%lx",
+ (int)pid, addr, addr + size);
+ fd = open(path, O_RDWR);
+ if (fd == -1) {
+ pr_perror("Can't open file %s", path);
+ exit(1);
+ }
+
+ mem = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ close(fd);
+ if (mem == MAP_FAILED) {
+ pr_perror("Can't map file %s", path);
+ exit(1);
+ }
+ return mem;
+}
+
+static void check_mem_eq(void *addr1, size_t size1, void *addr2, size_t size2)
+{
+ unsigned long min_size = size1 < size2 ? size1 : size2;
+
+ if (memcmp(addr1, addr2, min_size)) {
+ pr_err("Mem differs %lx %lx %lx", (unsigned long)addr1,
+ (unsigned long)addr2, min_size);
+ exit(1);
+ }
+}
+
+static void xmunmap(void *map, size_t size)
+{
+ if (munmap(map, size)) {
+ pr_err("xmunmap");
+ exit(1);
+ }
+}
+
+static void chk_proc_mem_eq(pid_t pid1, void *addr1, unsigned long size1,
+ pid_t pid2, void *addr2, unsigned long size2)
+{
+ void *map1, *map2;
+
+ map1 = mmap_proc_mem(pid1, (unsigned long)addr1, size1);
+ map2 = mmap_proc_mem(pid2, (unsigned long)addr2, size2);
+ check_mem_eq(map1, size1, map2, size2);
+ xmunmap(map1, size1);
+ xmunmap(map2, size2);
+}
+
+/*
+ * ps tree:
+ * proc1_______________
+ * | | |
+ * proc11___ proc12 proc13
+ * | | |
+ * proc111 proc112 proc131
+ */
+
+#define PROC1_PGIX 0
+#define PROC11_PGIX 1
+#define PROC12_PGIX 2
+#define PROC13_PGIX 3
+#define PROC111_PGIX 4
+#define PROC112_PGIX 5
+#define PROC131_PGIX 6
+#define ZERO_PGIX 7
+/* unused pgix: 8 */
+#define MEM_PERIOD (9 * PAGE_SIZE)
+
+struct pstree {
+ pid_t proc1;
+ pid_t proc11;
+ pid_t proc12;
+ pid_t proc13;
+ pid_t proc111;
+ pid_t proc112;
+ pid_t proc131;
+};
+struct pstree *pstree;
+
+size_t mem1_size = 1L << 20;
+size_t mem2_size = 1L << 21;
+size_t mem3_size = 1L << 22;
+uint8_t *mem1, *mem2, *mem3;
+
+#define CRC_EPOCH_OFFSET (PAGE_SIZE - sizeof(uint32_t))
+
+static void read_each_pg(volatile uint8_t *mem, size_t size, size_t off)
+{
+ if (!mem)
+ return;
+
+ while (off < size) {
+ (mem + off)[0];
+ off += MEM_PERIOD;
+ }
+}
+
+void datagen_each_pg(uint8_t *mem, size_t size, size_t off, uint32_t crc_epoch)
+{
+ if (!mem)
+ return;
+
+ while (test_go() && (off < size)) {
+ uint32_t crc = crc_epoch;
+
+ datagen(mem + off, CRC_EPOCH_OFFSET, &crc);
+ *(uint32_t *)(mem + off + CRC_EPOCH_OFFSET) = crc_epoch;
+ off += MEM_PERIOD;
+ }
+}
+
+void datachck_each_pg(uint8_t *mem, size_t size, size_t off)
+{
+ if (!mem)
+ return;
+
+ while (off < size) {
+ uint32_t crc = *(uint32_t *)(mem + off + CRC_EPOCH_OFFSET);
+
+ if (datachk(mem + off, CRC_EPOCH_OFFSET, &crc))
+ exit(1);
+ off += MEM_PERIOD;
+ }
+}
+
+static void mems_read_each_pgix(size_t pgix)
+{
+ const size_t off = pgix * PAGE_SIZE;
+
+ read_each_pg(mem1, mem1_size, off);
+ read_each_pg(mem2, mem2_size, off);
+ read_each_pg(mem3, mem3_size, off);
+}
+
+static void mems_datagen_each_pgix(size_t pgix, uint32_t *crc_epoch)
+{
+ const size_t off = pgix * PAGE_SIZE;
+
+ ++(*crc_epoch);
+ datagen_each_pg(mem1, mem1_size, off, *crc_epoch);
+ datagen_each_pg(mem2, mem2_size, off, *crc_epoch);
+ datagen_each_pg(mem3, mem3_size, off, *crc_epoch);
+}
+
+static void mems_datachck_each_pgix(size_t pgix)
+{
+ const size_t off = pgix * PAGE_SIZE;
+
+ datachck_each_pg(mem1, mem1_size, off);
+ datachck_each_pg(mem2, mem2_size, off);
+ datachck_each_pg(mem3, mem3_size, off);
+}
+
+static int proc131_func(task_waiter_t *setup_waiter)
+{
+ uint32_t crc_epoch = 0;
+
+ pstree->proc131 = getpid();
+ mems_datagen_each_pgix(PROC131_PGIX, &crc_epoch);
+ task_waiter_complete_current(setup_waiter);
+
+ while (test_go())
+ mems_datagen_each_pgix(PROC131_PGIX, &crc_epoch);
+ test_waitsig();
+
+ mems_datachck_each_pgix(PROC131_PGIX);
+ return 0;
+}
+
+static int proc13_func(task_waiter_t *setup_waiter)
+{
+ size_t MEM1_HOLE_START = 10 * MEM_PERIOD;
+ size_t MEM1_HOLE_SIZE = 3 * MEM_PERIOD;
+ uint32_t crc_epoch = 0;
+
+ pstree->proc13 = getpid();
+ xmunmap(mem1 + MEM1_HOLE_START, MEM1_HOLE_SIZE);
+ xmunmap(mem2, mem2_size);
+ xmunmap(mem3, mem3_size);
+ mem2 = mem1 + MEM1_HOLE_START + MEM1_HOLE_SIZE;
+ mem2_size = mem1_size - (mem2 - mem1);
+ mem1_size = MEM1_HOLE_START;
+ mem3 = mmap_ashmem(mem3_size);
+ mems_datagen_each_pgix(PROC13_PGIX, &crc_epoch);
+ fork_and_setup(proc131_func);
+ task_waiter_complete_current(setup_waiter);
+
+ while (test_go())
+ mems_datagen_each_pgix(PROC13_PGIX, &crc_epoch);
+ test_waitsig();
+
+ mems_datachck_each_pgix(PROC13_PGIX);
+
+ chk_proc_mem_eq(pstree->proc13, mem1, mem1_size,
+ pstree->proc131, mem1, mem1_size);
+ chk_proc_mem_eq(pstree->proc13, mem2, mem2_size,
+ pstree->proc131, mem2, mem2_size);
+ chk_proc_mem_eq(pstree->proc13, mem3, mem3_size,
+ pstree->proc131, mem3, mem3_size);
+
+ cont_and_wait_child(pstree->proc131);
+ return 0;
+}
+
+static int proc12_func(task_waiter_t *setup_waiter)
+{
+ uint32_t crc_epoch = 0;
+
+ pstree->proc12 = getpid();
+ mems_datagen_each_pgix(PROC12_PGIX, &crc_epoch);
+ task_waiter_complete_current(setup_waiter);
+
+ while (test_go())
+ mems_datagen_each_pgix(PROC12_PGIX, &crc_epoch);
+ test_waitsig();
+
+ mems_datachck_each_pgix(PROC12_PGIX);
+
+ return 0;
+}
+
+static int proc111_func(task_waiter_t *setup_waiter)
+{
+ uint32_t crc_epoch = 0;
+
+ pstree->proc111 = getpid();
+ mems_datagen_each_pgix(PROC111_PGIX, &crc_epoch);
+ task_waiter_complete_current(setup_waiter);
+
+ while (test_go())
+ mems_datagen_each_pgix(PROC111_PGIX, &crc_epoch);
+ test_waitsig();
+
+ mems_datachck_each_pgix(PROC111_PGIX);
+ return 0;
+}
+
+static int proc112_func(task_waiter_t *setup_waiter)
+{
+ uint32_t crc_epoch = 0;
+
+ pstree->proc112 = getpid();
+ mems_datagen_each_pgix(PROC112_PGIX, &crc_epoch);
+ task_waiter_complete_current(setup_waiter);
+
+ while (test_go())
+ mems_datagen_each_pgix(PROC112_PGIX, &crc_epoch);
+ test_waitsig();
+
+ mems_datachck_each_pgix(PROC112_PGIX);
+ return 0;
+}
+
+static int proc11_func(task_waiter_t *setup_waiter)
+{
+ const size_t MEM3_START_CUT = 5 * MEM_PERIOD;
+ const size_t MEM3_END_CUT = 2 * MEM_PERIOD;
+ void *mem3_old = mem3;
+ size_t mem3_size_old = mem3_size;
+ uint32_t crc_epoch = 0;
+
+ pstree->proc11 = getpid();
+ xmunmap(mem3, MEM3_START_CUT);
+ mem3 += MEM3_START_CUT;
+ mem3_size -= MEM3_START_CUT;
+ fork_and_setup(proc111_func);
+ fork_and_setup(proc112_func);
+ xmunmap(mem3 + mem3_size - MEM3_END_CUT, MEM3_END_CUT);
+ mem3_size -= MEM3_END_CUT;
+ mems_datagen_each_pgix(PROC11_PGIX, &crc_epoch);
+ task_waiter_complete_current(setup_waiter);
+
+ while (test_go())
+ mems_datagen_each_pgix(PROC11_PGIX, &crc_epoch);
+ test_waitsig();
+
+ mems_datachck_each_pgix(PROC11_PGIX);
+
+ chk_proc_mem_eq(pstree->proc11, mem1, mem1_size,
+ pstree->proc111, mem1, mem1_size);
+ chk_proc_mem_eq(pstree->proc11, mem1, mem1_size,
+ pstree->proc112, mem1, mem1_size);
+
+ chk_proc_mem_eq(pstree->proc11, mem2, mem2_size,
+ pstree->proc111, mem2, mem2_size);
+ chk_proc_mem_eq(pstree->proc11, mem2, mem2_size,
+ pstree->proc112, mem2, mem2_size);
+
+ chk_proc_mem_eq(pstree->proc11, mem3, mem3_size,
+ pstree->proc111, mem3, mem3_size + MEM3_END_CUT);
+ chk_proc_mem_eq(pstree->proc11, mem3, mem3_size,
+ pstree->proc112, mem3, mem3_size + MEM3_END_CUT);
+
+ uint8_t *proc1_mem3 = mmap_proc_mem(pstree->proc1,
+ (unsigned long)mem3_old, mem3_size_old);
+ check_mem_eq(mem3, mem3_size, proc1_mem3 + MEM3_START_CUT, mem3_size);
+ xmunmap(proc1_mem3, mem3_size_old);
+
+ cont_and_wait_child(pstree->proc111);
+ cont_and_wait_child(pstree->proc112);
+ return 0;
+}
+
+static int proc1_func(void)
+{
+ uint32_t crc_epoch = 0;
+ uint8_t *mem2_old = NULL;
+
+ pstree->proc1 = getpid();
+ mem1 = mmap_ashmem(mem1_size);
+ mem2 = mmap_ashmem(mem2_size);
+ mem3 = mmap_ashmem(mem3_size);
+ mems_datagen_each_pgix(PROC1_PGIX, &crc_epoch);
+ mems_read_each_pgix(ZERO_PGIX);
+
+ fork_and_setup(proc11_func);
+ fork_and_setup(proc12_func);
+ fork_and_setup(proc13_func);
+
+ xmunmap(mem1, mem1_size);
+ if (mremap(mem2, mem2_size, mem1_size, MREMAP_MAYMOVE | MREMAP_FIXED,
+ mem1) != mem1) {
+ pr_perror("proc1 mem2 remap");
+ exit(1);
+ }
+ mem2_old = mem2;
+ mem2 = NULL;
+
+ test_daemon();
+ while (test_go())
+ mems_datagen_each_pgix(PROC1_PGIX, &crc_epoch);
+ test_waitsig();
+
+ mems_datachck_each_pgix(PROC1_PGIX);
+
+ chk_proc_mem_eq(pstree->proc1, mem1, mem1_size,
+ pstree->proc11, mem2_old, mem2_size);
+ chk_proc_mem_eq(pstree->proc1, mem1, mem1_size,
+ pstree->proc12, mem2_old, mem2_size);
+
+ chk_proc_mem_eq(pstree->proc1, mem3, mem3_size,
+ pstree->proc12, mem3, mem3_size);
+
+ cont_and_wait_child(pstree->proc11);
+ cont_and_wait_child(pstree->proc12);
+ cont_and_wait_child(pstree->proc13);
+
+ pass();
+ return 0;
+}
+
+static void kill_pstree_from_root(void)
+{
+ if (getpid() != pstree->proc1)
+ return;
+
+ kill(pstree->proc11, SIGKILL);
+ kill(pstree->proc12, SIGKILL);
+ kill(pstree->proc13, SIGKILL);
+ kill(pstree->proc111, SIGKILL);
+ kill(pstree->proc112, SIGKILL);
+ kill(pstree->proc131, SIGKILL);
+}
+
+static void sigchld_hand(int signo, siginfo_t *info, void *ucontext)
+{
+ if (info->si_code != CLD_EXITED)
+ return;
+ if (!info->si_status)
+ return;
+
+ /* If we are not ps tree root then propagate child error to parent.
+ * If we are ps tree root then also call all
+ * atexit handlers set up by zdtm test framework and this test.
+ * exit() is not async signal safe but it's ok for testing purposes.
+ * exit() usage allows us to use very simple error handling
+ * and pstree killing logic.
+ */
+ exit(info->si_status);
+}
+
+int main(int argc, char **argv)
+{
+ test_init(argc, argv);
+
+ pstree = (struct pstree *)mmap_ashmem(PAGE_SIZE);
+
+ struct sigaction sa = {
+ .sa_sigaction = sigchld_hand,
+ .sa_flags = SA_RESTART | SA_SIGINFO | SA_NOCLDSTOP
+ };
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIGCHLD, &sa, NULL)) {
+ pr_perror("SIGCHLD handler setup");
+ exit(1);
+ };
+
+ if (atexit(kill_pstree_from_root)) {
+ pr_err("Can't setup atexit cleanup func");
+ exit(1);
+ }
+ return proc1_func();
+}
diff --git a/test/zdtm/transition/maps008.desc b/test/zdtm/transition/maps008.desc
new file mode 100644
index 0000000..fa2c82d
--- /dev/null
+++ b/test/zdtm/transition/maps008.desc
@@ -0,0 +1 @@
+{'flavor': 'h', 'flags': 'suid'}
--
1.9.1
More information about the CRIU
mailing list