Commit 40f56d5d authored by Eugene Batalov's avatar Eugene Batalov Committed by Pavel Emelyanov

test: add test for anon shmem dedup

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: 's avatarEugene Batalov <eabatalov89@gmail.com>
Signed-off-by: 's avatarPavel Emelyanov <xemul@virtuozzo.com>
parent eadf47c6
...@@ -4,8 +4,9 @@ source `dirname $0`/criu-lib.sh ...@@ -4,8 +4,9 @@ source `dirname $0`/criu-lib.sh
prep prep
./test/zdtm.py run --all --keep-going --report report --parallel 4 -f h --pre 2 --dedup -x maps04 -x maps007 || fail ./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 # 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/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/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
...@@ -15,6 +15,7 @@ TST_NOFILE = \ ...@@ -15,6 +15,7 @@ TST_NOFILE = \
fork2 \ fork2 \
thread-bomb \ thread-bomb \
maps007 \ maps007 \
maps008 \
pipe_loop00 \ pipe_loop00 \
pipe_shared00 \ pipe_shared00 \
socket_loop00 \ socket_loop00 \
......
#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();
}
{'flavor': 'h', 'flags': 'suid'}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment