[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