aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHaicheng Li <haicheng.li@intel.com>2009-10-27 10:57:42 +0800
committerAndi Kleen <ak@linux.intel.com>2009-10-27 05:32:46 +0100
commitdfa514615fed4ccb9d47708e12fbee4ee9795d92 (patch)
tree34f88f984dddc20b4514331cc0d1136e0b41bf30
parent661d6e0fbc5c61389a4d7328ed06ec5dbc574fd8 (diff)
downloadmce-test-dfa514615fed4ccb9d47708e12fbee4ee9795d92.tar.gz
page-poisoning workload: multi-process based test program thru madvise syscall.
page-poisoning test program is an extension of tinjpage test program with a multi-process model. It spawns thousands of processes that inject HWPosion error to various pages simultaneously thru madvise syscall. Then it checks if these errors get handled correctly, i.e. whether each test process receives or doesn't receive SIGBUS signal as expected. In details, page-poisoning is designed to cover all of possible userspace page types via following two test operations: - anonymous pages operations. - file data operations. Acked-by: Andi Kleen <andi.kleen@intel.com> Signed-off-by: Haicheng Li <haicheng.li@intel.com> Signed-off-by: Andi Kleen <ak@linux.intel.com>
-rw-r--r--stress/tools/Makefile17
-rw-r--r--stress/tools/page-poisoning/Makefile7
-rw-r--r--stress/tools/page-poisoning/README15
-rw-r--r--stress/tools/page-poisoning/page-poisoning.c886
4 files changed, 925 insertions, 0 deletions
diff --git a/stress/tools/Makefile b/stress/tools/Makefile
new file mode 100644
index 0000000..b4f666c
--- /dev/null
+++ b/stress/tools/Makefile
@@ -0,0 +1,17 @@
+SUBDIRS ?= fs-metadata page-poisoning
+
+all:
+ @for i in ${SUBDIRS}; do \
+ ${MAKE} -C $$i $@; \
+ done
+
+clean:
+ @for i in ${SUBDIRS}; do \
+ ${MAKE} -C $$i $@; \
+ done
+ rm -rf tmp result
+
+distclean:
+ @for i in ${SUBDIRS}; do \
+ ${MAKE} -C $$i $@; \
+ done
diff --git a/stress/tools/page-poisoning/Makefile b/stress/tools/page-poisoning/Makefile
new file mode 100644
index 0000000..ccfccb0
--- /dev/null
+++ b/stress/tools/page-poisoning/Makefile
@@ -0,0 +1,7 @@
+all:
+ gcc -Wall -g page-poisoning.c -o page-poisoning
+
+ cp page-poisoning ../../bin
+
+clean:
+ rm -f page-poisoning *.o
diff --git a/stress/tools/page-poisoning/README b/stress/tools/page-poisoning/README
new file mode 100644
index 0000000..58c11d7
--- /dev/null
+++ b/stress/tools/page-poisoning/README
@@ -0,0 +1,15 @@
+Multi-process based page-poisoning test program.
+
+page-poisoning test program is an extension of tinjpage test program with a
+multi-process model. It spawns thousands of processes that inject HWPosion
+error to various pages simultaneously thru madvise syscall. Then it checks
+if these errors get handled correctly, i.e. whether each test process
+receives or doesn't receive SIGBUS signal as expected.
+
+In details, page-poisoning is designed to cover all of possible userspace page
+types via following two test operations:
+ - anonymous pages operations.
+ - file data operations.
+
+You can get detailed help info in following way:
+# ./page-poisoning -h
diff --git a/stress/tools/page-poisoning/page-poisoning.c b/stress/tools/page-poisoning/page-poisoning.c
new file mode 100644
index 0000000..49b90ea
--- /dev/null
+++ b/stress/tools/page-poisoning/page-poisoning.c
@@ -0,0 +1,886 @@
+/*
+ * Test program for Linux poison memory error recovery.
+ * This program is extended from tinjpage with a multi-process model.
+ *
+ * This injects poison into various mapping cases and triggers the poison
+ * handling. Requires special injection support in the kernel.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; version
+ * 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should find a copy of v2 of the GNU General Public License somewhere
+ * on your Linux system; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors: Andi Kleen, Fengguang Wu, Haicheng Li
+ *
+ */
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <getopt.h>
+
+#include <sys/mman.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/wait.h>
+
+#define MADV_POISON 100
+
+#define PAGE_SIZE 4 * 1024
+#define SHM_SIZE 1 // in page.
+#define SHM_MODE 0600
+#define FILE_SIZE 1 * 1024 * 1024 * 1024
+#define FILE_PATH 100
+#define LOG_BUFLEN 100
+
+#define INSTANCE_NUM 10000
+
+#define TEST_PASS 1
+#define TEST_FAIL 0
+
+static int PS = PAGE_SIZE;
+static int instance = 0; // idex of the child process.
+static int testid = 0; // test idex of the child process.
+static int test_types = 0; // totoal test types.
+static int t_shm = -1; // index of shm test case.
+static int failure = 0; // result of child process.
+static int unexpected = 0; // result of child process.
+static int early_kill = 0;
+struct test {
+ int id;
+ int result;
+};
+struct shm {
+ int id;
+ int ready;
+ int done;
+};
+struct ipc {
+ struct test test[INSTANCE_NUM];
+ struct shm shm;
+};
+static int ipc_entry;
+static int *shmptr = NULL;
+
+static pid_t g_pid[INSTANCE_NUM] = { 0 };
+static int shm_size = SHM_SIZE;
+static int child_num = INSTANCE_NUM;
+static int shm_child_num = 0;
+static char log_file[FILE_PATH];
+static FILE *log_fd = NULL;
+static char result_file[FILE_PATH];
+static FILE *result_fd = NULL;
+static char tmp_dir[FILE_PATH] = { '\0' };
+static int clean_env = 0;
+
+static int semid_ready = 0;
+
+static pid_t mypid;
+
+union semun {
+ int val;
+ struct semid_ds *buf;
+ unsigned short int *array;
+ struct semid_info *__buf;
+};
+
+enum rmode {
+ MREAD = 0,
+ MWRITE = 1,
+ MREAD_OK = 2,
+ MWRITE_OK = 3,
+ MNOTHING = -1,
+};
+
+static struct option opts[] = {
+ {"clean", 0, 0, 'C'},
+ {"help", 0, 0, 'h'},
+ {"instance", 0, 0, 'i'},
+ {"log", 0, 0, 'l'},
+ {"result", 0, 0, 'r'},
+ {"shmsize", 0, 0, 's'},
+ {"tmpdir", 0, 0, 't'},
+ {"", 0, 0, '\0'}
+};
+
+static void help(void)
+{
+ printf("Usage: page-poisoning [OPTION]...\n"
+ "Stress test for Linux HWPOISON Page Recovery with multiple processes.\n"
+ "\n"
+ "Mandatory arguments to long options are mandatory for short options too.\n"
+ " -C, --clean record log and result in clean files.\n"
+ " -h print this page\n"
+ " -i, --child_num=NUM spawn NUM processes to do test (default NUM = %d)\n"
+ " -l, --log=LOG record logs to file LOG.\n"
+ " -r, --result=RESULT record test result to file RESULT.\n"
+ " -s, --shmsize=SIZE each shared memory segment is SIZE-page based.\n"
+ " -t, --tmpdir=DIR create temporary files in DIR.\n\n",
+ INSTANCE_NUM);
+}
+
+static void err(const char *fmt, ...);
+static void mylog(const char *fmt, ...)
+{
+ char buf[LOG_BUFLEN] = { '\0' };
+ va_list args;
+ if (!log_fd)
+ err("no log file there\n");
+
+ va_start(args, fmt);
+ vsprintf(buf, fmt, args);
+ printf("[pid %d] %s", mypid, buf);
+ fprintf(log_fd, "[pid %d] %s", mypid, buf);
+ fflush(log_fd);
+ va_end(args);
+}
+
+static void result(const char *fmt, ...)
+{
+ char buf[LOG_BUFLEN] = { '\0' };
+ va_list args;
+ if (!result_fd)
+ err("no result file there\n");
+
+ va_start(args, fmt);
+ vsprintf(buf, fmt, args);
+ fprintf(result_fd, "[pid %d] %s", mypid, buf);
+ fflush(result_fd);
+ if (log_fd)
+ mylog("%s", buf);
+ va_end(args);
+}
+
+static void err(const char *fmt, ...)
+{
+ char buf[LOG_BUFLEN] = { '\0' };
+ va_list args;
+ va_start(args, fmt);
+
+ vsprintf(buf, fmt, args);
+ if (result_fd)
+ result("error: %s :%s\n", buf, strerror(errno));
+ else
+ perror(buf);
+ va_end(args);
+ exit(1);
+}
+
+static void *checked_mmap(void *start, size_t length, int prot, int flags,
+ int fd, off_t offset)
+{
+ void *map = mmap(start, length, prot, flags, fd, offset);
+ if (map == (void *)-1L)
+ err("mmap");
+ return map;
+}
+
+static void munmap_reserve(void *page, int size)
+{
+ munmap(page, size);
+ mmap(page, size, PROT_NONE, MAP_PRIVATE | MAP_FIXED, 0, 0);
+}
+
+static void *xmalloc(size_t s)
+{
+ void *p = malloc(s);
+ if (!p)
+ exit(ENOMEM);
+ return p;
+}
+
+static int recovercount;
+static sigjmp_buf recover_ctx;
+static sigjmp_buf early_recover_ctx;
+static void *expected_addr;
+
+static void sighandler(int sig, siginfo_t * si, void *arg)
+{
+ mylog("signal %d code %d addr %p\n", sig, si->si_code, si->si_addr);
+ if (si->si_addr != expected_addr) {
+ result
+ ("failed: Unexpected address in signal %p (expected %p)\n",
+ si->si_addr, expected_addr);
+ failure++;
+ }
+
+ if (--recovercount == 0) {
+ result("failed: I seem to be in a signal loop. bailing out.\n");
+ exit(1);
+ }
+
+ if (si->si_code == 4)
+ siglongjmp(recover_ctx, 1);
+ else
+ siglongjmp(early_recover_ctx, 1);
+}
+
+static void poison(char *msg, char *page, enum rmode mode)
+{
+ expected_addr = page;
+ recovercount = 5;
+
+ if (sigsetjmp(early_recover_ctx, 1) == 0) {
+ if (madvise(page, PS, MADV_POISON) != 0) {
+ if (errno == EINVAL) {
+ result("failed: Kernel doesn't support poison injection\n");
+ exit(0);
+ }
+ err("error: madvise: %s", strerror(errno));
+ return;
+ }
+
+ if (early_kill && (mode == MWRITE || mode == MREAD)) {
+ result("failed: %s: process is not early killed\n",
+ msg);
+ failure++;
+ }
+
+ return;
+ }
+
+ if (early_kill) {
+ if (mode == MREAD_OK || mode == MWRITE_OK) {
+ result("failed: %s: killed\n", msg);
+ failure++;
+ } else
+ mylog("pass: recovered\n");
+ }
+}
+
+static void recover(char *msg, char *page, enum rmode mode)
+{
+ expected_addr = page;
+ recovercount = 5;
+
+ if (sigsetjmp(recover_ctx, 1) == 0) {
+ switch (mode) {
+ case MWRITE:
+ mylog("writing 2\n");
+ *page = 2;
+ break;
+ case MWRITE_OK:
+ mylog("writing 4\n");
+ *page = 4;
+ return;
+ case MREAD:
+ mylog("reading %x\n", *(unsigned char *)page);
+ break;
+ case MREAD_OK:
+ mylog("reading %x\n", *(unsigned char *)page);
+ return;
+ case MNOTHING:
+ return;
+ }
+ /* signal or kill should have happened */
+ result("failed: %s: page not poisoned after injection\n", msg);
+ failure++;
+ return;
+ }
+ if (mode == MREAD_OK || mode == MWRITE_OK) {
+ result("failed: %s: killed\n", msg);
+ failure++;
+ } else
+ mylog("pass: recovered\n");
+}
+
+static void testmem(char *msg, char *page, enum rmode mode)
+{
+ mylog("%s poisoning page %p\n", msg, page);
+ poison(msg, page, mode);
+ recover(msg, page, mode);
+}
+
+static void expecterr(char *msg, int err)
+{
+ if (err) {
+ mylog("pass: expected error %d on %s\n", errno, msg);
+ } else {
+ result("failed: unexpected no error on %s\n", msg);
+ failure++;
+ }
+}
+
+/*
+ * Any optional error is really a deficiency in the kernel VFS error reporting
+ * and should be eventually fixed and turned into a expecterr
+ */
+static void optionalerr(char *msg, int err)
+{
+ if (err) {
+ mylog("pass: expected error %d on %s\n", errno, msg);
+ } else {
+ mylog("LATER: expected likely incorrect no error on %s\n", msg);
+ unexpected++;
+ }
+}
+
+static int playfile(char *buf)
+{
+ int fd;
+ if (buf[0] == 0)
+ snprintf(buf, FILE_PATH, "%s/poison%d", tmp_dir, mypid);
+ fd = open(buf, O_CREAT | O_RDWR | O_TRUNC, 0600);
+ if (fd < 0)
+ err("opening temporary file: %s", buf);
+
+ const int NPAGES = 5;
+ char *tmp = xmalloc(PS * NPAGES);
+ int i;
+ for (i = 0; i < PS * NPAGES; i++)
+ tmp[i] = i;
+ write(fd, tmp, PS * NPAGES);
+
+ lseek(fd, 0, SEEK_SET);
+ return fd;
+}
+
+static void dirty_anonymous(void)
+{
+ struct ipc *ipc;
+ char *page;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, 0, 0);
+ testmem("dirty", page, MWRITE);
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static void dirty_anonymous_unmap(void)
+{
+ struct ipc *ipc;
+ char *page;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, 0, 0);
+ testmem("dirty", page, MWRITE);
+ munmap_reserve(page, PS);
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static void mlocked_anonymous(void)
+{
+ struct ipc *ipc;
+ char *page;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, 0, 0);
+ testmem("mlocked", page, MWRITE);
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static void do_file_clean(int flags, char *name)
+{
+ char *page;
+ char fn[FILE_PATH];
+ snprintf(fn, 30, "%s/test%d", tmp_dir, mypid);
+ int fd = open(fn, O_RDWR | O_TRUNC | O_CREAT);
+ if (fd < 0)
+ err("opening temporary file: %s", fn);
+ write(fd, fn, 4);
+ page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE, MAP_SHARED | flags,
+ fd, 0);
+ fsync(fd);
+ close(fd);
+ testmem(name, page, MREAD_OK);
+ /* reread page from disk */
+ mylog("reading %x\n", *(unsigned char *)page);
+ testmem(name, page, MWRITE_OK);
+}
+
+static void file_clean(void)
+{
+ struct ipc *ipc;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ do_file_clean(0, "file clean");
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static void file_clean_mlocked(void)
+{
+ struct ipc *ipc;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ do_file_clean(MAP_LOCKED, "file clean mlocked");
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static char *ndesc(char *buf, char *name, char *add)
+{
+ snprintf(buf, 100, "%s %s", name, add);
+ return buf;
+}
+
+static void do_file_dirty(int flags, char *name)
+{
+ char nbuf[100];
+ char *page;
+ char fn[FILE_PATH];
+ fn[0] = 0;
+ int fd = playfile(fn);
+
+ page = checked_mmap(NULL, PS, PROT_READ,
+ MAP_SHARED | MAP_POPULATE | flags, fd, 0);
+ testmem(ndesc(nbuf, name, "initial"), page, MREAD);
+ expecterr("msync expect error", msync(page, PS, MS_SYNC) < 0);
+ close(fd);
+ munmap_reserve(page, PS);
+
+ fd = open(fn, O_RDONLY);
+ if (fd < 0)
+ err("reopening temp file");
+ page = checked_mmap(NULL, PS, PROT_READ, MAP_SHARED | MAP_POPULATE | flags,
+ fd, 0);
+ recover(ndesc(nbuf, name, "populated"), page, MREAD_OK);
+ close(fd);
+ munmap_reserve(page, PS);
+
+ fd = open(fn, O_RDONLY);
+ if (fd < 0)
+ err("reopening temp file");
+ page = checked_mmap(NULL, PS, PROT_READ, MAP_SHARED | flags, fd, 0);
+ recover(ndesc(nbuf, name, "fault"), page, MREAD_OK);
+ close(fd);
+ munmap_reserve(page, PS);
+
+ fd = open(fn, O_RDWR);
+ char buf[128];
+ /* the earlier close has eaten the error */
+ optionalerr("explicit read after poison",
+ read(fd, buf, sizeof buf) < 0);
+ optionalerr("explicit write after poison", write(fd, "foobar", 6) < 0);
+ optionalerr("fsync expect error", fsync(fd) < 0);
+ close(fd);
+
+ /* should unlink return an error here? */
+ if (unlink(fn) < 0)
+ perror("unlink");
+}
+
+static void file_dirty(void)
+{
+ struct ipc *ipc;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ do_file_dirty(0, "file dirty");
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static void file_dirty_mlocked(void)
+{
+ struct ipc *ipc;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ do_file_dirty(MAP_LOCKED, "file dirty mlocked");
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static void request_sem(int id, int num)
+{
+ struct sembuf sb;
+
+ sb.sem_num = num;
+ sb.sem_op = -1;
+ sb.sem_flg = 0;
+
+ semop(id, &sb, 1);
+}
+
+static void waiton_sem(int id, int num)
+{
+ struct sembuf sb;
+
+ sb.sem_num = num;
+ sb.sem_flg = 0;
+
+ sb.sem_op = -1;
+ semop(id, &sb, 1);
+ sb.sem_op = 0;
+ semop(id, &sb, 1);
+}
+
+static void release_sem(int id, int num)
+{
+ struct sembuf sb;
+
+ sb.sem_num = num;
+ sb.sem_op = 1;
+ sb.sem_flg = 0;
+
+ semop(id, &sb, 1);
+}
+
+static void clean_anonymous(void)
+{
+ char *page;
+ page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+ testmem("clean", page, MWRITE_OK);
+}
+
+static void anon_clean(void)
+{
+ struct ipc *ipc;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ clean_anonymous();
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+/* TBD
+static void survival(void)
+{
+ struct ipc *ipc;
+ char page;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+ testmem("survial", &page, MNOTHING);
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+*/
+
+static void shm_test(void)
+{
+ struct ipc *ipc;
+
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ ipc->test[instance].id = testid;
+
+ request_sem(semid_ready, 0);
+ if (!ipc->shm.ready) {
+ if ((ipc->shm.id = shmget(IPC_PRIVATE, shm_size * PS,
+ SHM_MODE)) < 0)
+ err("shmget error\n");
+ ipc->shm.ready = 1;
+ }
+ if ((shmptr = shmat(ipc->shm.id, 0, 0)) == (void *)-1) {
+ err("shmat error\n");
+ } else
+ *shmptr = mypid;
+ release_sem(semid_ready, 0);
+
+ waiton_sem(semid_ready, 1);
+
+ request_sem(semid_ready, 0);
+ if (!ipc->shm.done) {
+ ipc->shm.done = 1;
+ testmem("shm dirty", (char *)shmptr, MWRITE);
+ } else
+ recover("shm dirty", (char *)shmptr, MREAD);
+ release_sem(semid_ready, 0);
+
+ if (!failure)
+ ipc->test[instance].result = TEST_PASS;
+ shmdt(ipc);
+}
+
+static void setup_ipc(void)
+{
+ int size;
+ union semun sunion;
+ struct ipc *ipc;
+
+ size = sizeof(struct ipc);
+
+ if ((ipc_entry = shmget(IPC_PRIVATE, size, SHM_MODE)) < 0)
+ err("shmget error\n");
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+ memset(ipc, 0, sizeof(struct ipc));
+
+ semid_ready = semget(IPC_PRIVATE, 2, SHM_R | SHM_W);
+ sunion.val = 1;
+ semctl(semid_ready, 0, SETVAL, sunion);
+ if (t_shm != -1) {
+ if (((child_num - 1) % test_types) >= t_shm)
+ shm_child_num = (child_num - 1) / test_types + 1;
+ else
+ shm_child_num = (child_num - 1) / test_types;
+ }
+ if (shm_child_num) {
+ sunion.val = shm_child_num;
+ semctl(semid_ready, 1, SETVAL, sunion);
+ mylog("there are %d shm_child\n", shm_child_num);
+ }
+}
+
+static void free_ipc(void)
+{
+ semctl(semid_ready, 0, IPC_RMID);
+ shmctl(ipc_entry, IPC_RMID, 0);
+}
+
+static void cleanup(void)
+{
+ int i;
+ for (i = 0; i < instance; i++)
+ kill(g_pid[i], 9); //kill the suviving child.
+ free_ipc();
+}
+
+struct testcase {
+ void (*f) (void);
+ char *name;
+ int survivable;
+} cases[] = {
+ {
+ shm_test, "shared memory test", 0}, {
+ anon_clean, "anonymous clean", 1}, {
+ dirty_anonymous, "anonymous dirty", 0}, {
+ dirty_anonymous_unmap, "anonymous dirty unmap", 0}, {
+ mlocked_anonymous, "anonymous dirty mlocked", 0}, {
+ file_clean, "file clean", 1}, {
+ file_dirty, "file dirty", 0}, {
+ file_clean_mlocked, "file clean mlocked", 1}, {
+ file_dirty_mlocked, "file dirty mlocked", 0},
+// { survival, "survival", 0 },
+ {
+ NULL, NULL, 0}
+};
+
+static int run_test(int children)
+{
+ pid_t pid = -1;
+ int i = 0, rc = 0;
+ siginfo_t sig;
+ struct ipc *ipc;
+
+ for (i = 0; i < children; i++) {
+ pid = fork();
+ if (pid < 0) {
+ err("fork %d\n", i);
+ break;
+ } else if (pid == 0) {
+ int j = instance % test_types;
+ mypid = getpid();
+ testid = j;
+ cases[j].f();
+ exit(0);
+ } else {
+ g_pid[i] = pid;
+ ++instance;
+ fflush(stdout);
+ }
+ }
+
+ mylog("have spawned %d processes\n", instance);
+ if (instance) {
+ if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1)
+ err("shmat error\n");
+
+ for (i = 0; i < instance; i++) {
+ int j = i % test_types;
+ int t = ipc->test[j].id;
+
+ mylog("wait for Pid %d\n", g_pid[i]);
+ waitid(P_PID, g_pid[i], &sig, WEXITED);
+ if (ipc->test[j].result == TEST_PASS)
+ result("Ins %d: Pid %d: pass - %s\n", i,
+ g_pid[i], cases[t].name);
+ else {
+ result("Ins %d: Pid %d: failed - %s\n", i,
+ g_pid[i], cases[t].name);
+ failure++;
+ }
+ }
+ }
+
+ if (!failure)
+ result("\t!!! MADVISE Test got PASS. !!!\n\n");
+ else {
+ result
+ ("\t!!! MADVISE Test is FAILED (%d failures found). !!!\n\n",
+ failure);
+ rc = 1;
+ }
+
+ return rc;
+}
+
+static void setup_log(void)
+{
+ int rc = 0;
+ if (clean_env)
+ log_fd = fopen(log_file, "w");
+ else
+ log_fd = fopen(log_file, "a");
+ if (!log_fd)
+ err("cannot open log file: %s\n", log_file);
+
+ if (clean_env)
+ result_fd = fopen(result_file, "w");
+ else
+ result_fd = fopen(result_file, "a");
+ if (!result_fd)
+ err("cannot open log file: %s\n", result_file);
+
+ if (tmp_dir[0] != '\0') {
+ rc = mkdir(tmp_dir, 0777);
+ if (rc && errno != EEXIST)
+ err("cannot create tmp dir: %s: %s\n", tmp_dir,
+ strerror(errno));
+ }
+}
+
+static void free_log(void)
+{
+ fclose(log_fd);
+ fclose(result_fd);
+}
+
+static void main_sighandler(int sig, siginfo_t * si, void *arg)
+{
+ mylog("receive signal to get terminated\n");
+ cleanup();
+ exit(1);
+}
+
+static void setup_sig(void)
+{
+ struct sigaction sa = {
+ .sa_sigaction = main_sighandler,
+ .sa_flags = SA_SIGINFO
+ };
+ struct sigaction sa_bus = {
+ .sa_sigaction = sighandler,
+ .sa_flags = SA_SIGINFO
+ };
+
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGKILL, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGBUS, &sa_bus, NULL);
+}
+
+static void setup_test(void)
+{
+ struct testcase *t;
+ /* catch signals */
+ for (t = cases; t->f; t++)
+ if (t->f == shm_test)
+ t_shm = (t - cases);
+ test_types = t - cases;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = 0, c, opt_index;
+
+ snprintf(log_file, sizeof(log_file), "page-poisoning.log");
+ snprintf(result_file, sizeof(result_file), "page-poisoning.result");
+ snprintf(tmp_dir, sizeof(tmp_dir), "./tmp");
+ while (1) {
+ c = getopt_long(argc, argv, "Chi:l:r:s:t:", opts, &opt_index);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'C':
+ clean_env = 1;
+ break;
+ case 'h':
+ help();
+ return 0;
+ case 'i':
+ child_num = strtol(optarg, NULL, 0);
+ if (child_num > INSTANCE_NUM)
+ child_num = INSTANCE_NUM;
+ break;
+ case 'l':
+ snprintf(log_file, sizeof(log_file), "%s", optarg);
+ break;
+ case 'r':
+ snprintf(result_file, sizeof(result_file), "%s",
+ optarg);
+ break;
+ case 's':
+ shm_size = strtol(optarg, NULL, 0);
+ if (shm_size < SHM_SIZE)
+ shm_size = SHM_SIZE;
+ break;
+ case 't':
+ snprintf(tmp_dir, sizeof(tmp_dir), "%s", optarg);
+ break;
+ default:
+ help();
+ return 0;
+ }
+ }
+
+ if (!early_kill)
+ system("sysctl -w vm.memory_failure_early_kill=0");
+ mypid = getpid();
+ setup_log();
+ setup_test();
+ if (!child_num) {
+ mylog("end without test executed since child_num = 0\n");
+ return rc;
+ }
+
+ mylog("start page-poisoning test\n");
+ PS = getpagesize();
+ setup_ipc();
+ setup_sig();
+ rc = run_test(child_num);
+ free_ipc();
+ mylog("page-poisoning test done!\n");
+ free_log();
+
+ return rc;
+}