diff --git a/kern/conf/conf.kern b/kern/conf/conf.kern index 2a29022..3fa5742 100644 --- a/kern/conf/conf.kern +++ b/kern/conf/conf.kern @@ -461,3 +461,6 @@ defoption synchprobs optfile synchprobs synchprobs/whalemating.c optfile synchprobs synchprobs/stoplight.c optfile synchprobs test/synchprobs.c + +defoption automationtest +optfile automationtest test/automationtest.c diff --git a/kern/include/lib.h b/kern/include/lib.h index d1ae75d..a5aca71 100644 --- a/kern/include/lib.h +++ b/kern/include/lib.h @@ -201,9 +201,10 @@ void random_spinner(uint32_t); /* * Testing variants of kprintf. tprintf is silent during automated testing. * sprintf prefixes the kernel secret to kprintf messages during automated - * testing. + * testing. nprintf is not silent during automated testing. */ int tkprintf(const char *format, ...) __PF(1,2); +int nkprintf(const char *format, ...) __PF(1,2); #endif /* _LIB_H_ */ diff --git a/kern/include/test.h b/kern/include/test.h index a2458a2..260e515 100644 --- a/kern/include/test.h +++ b/kern/include/test.h @@ -31,6 +31,7 @@ #define _TEST_H_ #include "opt-synchprobs.h" +#include "opt-automationtest.h" /* * Declarations for test code and other miscellaneous high-level @@ -133,4 +134,14 @@ int stoplight(int, char **); #endif +/* + * Automation tests for detecting kernel deadlocks and livelocks. + */ + +#if OPT_AUTOMATIONTEST +int dltest(int, char **); +int ll1test(int, char **); +int ll16test(int, char **); +#endif + #endif /* _TEST_H_ */ diff --git a/kern/lib/kprintf.c b/kern/lib/kprintf.c index c8a3baa..0a6fe90 100644 --- a/kern/lib/kprintf.c +++ b/kern/lib/kprintf.c @@ -159,6 +159,25 @@ tkprintf(const char *fmt, ...) return chars; } +/* + * kprintf variant that is quiet during non-automated testing + */ +int +nkprintf(const char *fmt, ...) +{ + int chars; + va_list ap; + + if (strcmp(KERNEL_SECRET, "") == 0) { + return 0; + } + + va_start(ap, fmt); + chars = vkprintf(fmt, ap); + va_end(ap); + + return chars; +} /* * panic() is for fatal errors. It prints the printf arguments it's diff --git a/kern/main/menu.c b/kern/main/menu.c index d0ed255..abb99cb 100644 --- a/kern/main/menu.c +++ b/kern/main/menu.c @@ -48,6 +48,7 @@ #include "opt-sfs.h" #include "opt-net.h" #include "opt-synchprobs.h" +#include "opt-automationtest.h" /* * In-kernel menu and command dispatcher. @@ -530,6 +531,29 @@ cmd_testmenu(int n, char **a) return 0; } +#if OPT_AUTOMATIONTEST +static const char *automationmenu[] = { + "[dl] Deadlock test (*) ", + "[ll1] Livelock test (1 thread) ", + "[ll16] Livelock test (16 threads) ", + NULL +}; + +static +int +cmd_automationmenu(int n, char **a) +{ + (void)n; + (void)a; + + showmenu("OS/161 automation tests menu", automationmenu); + kprintf(" (*) These tests require locks.\n"); + kprintf("\n"); + + return 0; +} +#endif + static const char *mainmenu[] = { "[?o] Operations menu ", "[?t] Tests menu ", @@ -566,6 +590,9 @@ static struct { { "help", cmd_mainmenu }, { "?o", cmd_opsmenu }, { "?t", cmd_testmenu }, +#if OPT_AUTOMATIONTEST + { "?a", cmd_automationmenu }, +#endif /* operations */ { "s", cmd_shell }, @@ -654,6 +681,13 @@ static struct { { "fs4", writestress2 }, { "fs5", longstress }, { "fs6", createstress }, + +#if OPT_AUTOMATIONTEST + /* automation tests */ + { "dl", dltest }, + { "ll1", ll1test }, + { "ll16", ll16test }, +#endif { NULL, NULL } }; diff --git a/kern/test/automationtest.c b/kern/test/automationtest.c new file mode 100644 index 0000000..2dce056 --- /dev/null +++ b/kern/test/automationtest.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009 + * The President and Fellows of Harvard College. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Automation test code for creating (and detecting) kernel dead and livelocks. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_SPINNERS 16 + +static struct lock *deadlock_locks[2]; +static struct semaphore *deadlock_sem; + +struct spinlock spinners_lock[MAX_SPINNERS]; + +static +void +inititems(void) +{ + int i; + + for (i = 0; i < 2; i++) { + deadlock_locks[i] = lock_create("deadlock lock"); + if (deadlock_locks[i] == NULL) { + panic("automationtest: lock_create failed\n"); + } + } + deadlock_sem = sem_create("deadlock sem", 0); + if (deadlock_sem == NULL) { + panic("automationtest: sem_create failed\n"); + } + + for (i = 0; i < MAX_SPINNERS; i++) { + spinlock_init(&(spinners_lock[i])); + } +} + +static +void +dltestthread(void *junk1, unsigned long junk2) +{ + (void)junk1; + (void)junk2; + + lock_acquire(deadlock_locks[1]); + V(deadlock_sem); + lock_acquire(deadlock_locks[0]); +} + +int +dltest(int nargs, char **args) +{ + int result; + + (void)nargs; + (void)args; + + inititems(); + + lock_acquire(deadlock_locks[0]); + + result = thread_fork("dltest", NULL, dltestthread, NULL, (unsigned long)0); + if (result) { + panic("dltest: thread_fork failed: %s\n", strerror(result)); + } + + P(deadlock_sem); + lock_acquire(deadlock_locks[1]); + + panic("dltest: didn't create deadlock (locks probably don't work)\n"); + + // 09 Jan 2015 : GWA : Shouldn't return. + return 0; +} + +inline +static +void +infinite_spinner(unsigned long i) +{ + (void)i; + volatile int j; + + for (j=0; j<10000000; j++); + + spinlock_acquire(&(spinners_lock[i])); + + for (j=0; j<=1000; j++) { + if (j == 1000) { + j = 0; + } + } + panic("ll1test: infinite spin loop completed\n"); +} + +int +ll1test(int nargs, char **args) +{ + (void)nargs; + (void)args; + + inititems(); + + infinite_spinner((unsigned long) 0); + + // 09 Jan 2015 : GWA : Shouldn't return. + return 0; +} + + +static +void +ll16testthread(void *junk1, unsigned long i) +{ + (void)junk1; + + infinite_spinner(i); +} + +int +ll16test(int nargs, char **args) +{ + int i, result; + + inititems(); + + (void)nargs; + (void)args; + + for (i=1; i<16; i++) { + result = thread_fork("ll16testthread", NULL, ll16testthread, NULL, (unsigned long)i); + if (result) { + panic("ll16test: thread_fork failed: %s\n", strerror(result)); + } + } + infinite_spinner(0); + + // 09 Jan 2015 : GWA : Shouldn't return. + return 0; +} diff --git a/userland/include/stdio.h b/userland/include/stdio.h index 440a822..772277f 100644 --- a/userland/include/stdio.h +++ b/userland/include/stdio.h @@ -59,6 +59,7 @@ int vsnprintf(char *buf, size_t len, const char *fmt, __va_list ap); /* Automated testing extensions. */ int tprintf(const char *fmt, ...); +int nprintf(const char *fmt, ...); int printsf(const char *fmt, ...); /* Print the argument string and then a newline. Returns 0 or -1 on error. */ diff --git a/userland/lib/libc/stdio/printf.c b/userland/lib/libc/stdio/printf.c index 52a8161..30d923d 100644 --- a/userland/lib/libc/stdio/printf.c +++ b/userland/lib/libc/stdio/printf.c @@ -97,6 +97,24 @@ tprintf(const char *fmt, ...) return chars; } +/* printf variant that is loud during automated testing */ +int +nprintf(const char *fmt, ...) +{ + int chars; + va_list ap; + + if (strcmp(KERNEL_SECRET, "") == 0) { + return 0; + } + + va_start(ap, fmt); + chars = vprintf(fmt, ap); + va_end(ap); + + return chars; +} + /* printf variant that prepends the kernel secret */ int printsf(const char *fmt, ...) diff --git a/userland/testbin/Makefile b/userland/testbin/Makefile index e5cf502..3e16de0 100644 --- a/userland/testbin/Makefile +++ b/userland/testbin/Makefile @@ -10,8 +10,8 @@ SUBDIRS=add argtest badcall bigexec bigfile bigfork bigseek bloat conman \ filetest forkbomb forktest frack guzzle hash hog huge kitchen \ malloctest matmult multiexec palin parallelvm poisondisk psort \ quinthuge quintmat quintsort randcall redirect rmdirtest rmtest \ - sbrktest schedpong sink sort sparsefile sty tail tictac triplehuge \ - triplemat triplesort usemtest zero + sbrktest schedpong sink sort sparsefile spinner sty tail tictac \ + triplehuge triplemat triplesort usemtest waiter zero # But not: # userthreads (no support in kernel API in base system) diff --git a/userland/testbin/spinner/Makefile b/userland/testbin/spinner/Makefile new file mode 100644 index 0000000..2eaac72 --- /dev/null +++ b/userland/testbin/spinner/Makefile @@ -0,0 +1,11 @@ +# Makefile for spinner + +TOP=../../.. +.include "$(TOP)/mk/os161.config.mk" + +PROG=spinner +SRCS=spinner.c +BINDIR=/testbin + +.include "$(TOP)/mk/os161.prog.mk" + diff --git a/userland/testbin/spinner/spinner.c b/userland/testbin/spinner/spinner.c new file mode 100644 index 0000000..cffb3aa --- /dev/null +++ b/userland/testbin/spinner/spinner.c @@ -0,0 +1,47 @@ +/* + * spinner.c + * + * Spins as hard as it can, forking multiple processes as needed. Intended to + * test our ability to detect stuck processes in userspace. + */ + +#include +#include +#include +#include + +static +void +spin(void) +{ + volatile int i; + + for (i=0; i <= 1000; i++) { + if (i == 1000) { + i = 0; + } + } +} + +int +main(int argc, char **argv) +{ + int i, count, pid; + + if (argc != 2) { + errx(1, "Usage: spinner "); + } + count = atoi(argv[1]); + + for (i = 1; i < count; i++) { + pid = fork(); + if (pid != 0) { + spin(); + } + } + spin(); + errx(2, "spinner: spin returned"); + + // 09 Jan 2015 : GWA : Shouldn't get here. + return 0; +} diff --git a/userland/testbin/waiter/Makefile b/userland/testbin/waiter/Makefile new file mode 100644 index 0000000..fdad038 --- /dev/null +++ b/userland/testbin/waiter/Makefile @@ -0,0 +1,11 @@ +# Makefile for waiter + +TOP=../../.. +.include "$(TOP)/mk/os161.config.mk" + +PROG=waiter +SRCS=waiter.c +BINDIR=/testbin + +.include "$(TOP)/mk/os161.prog.mk" + diff --git a/userland/testbin/waiter/waiter.c b/userland/testbin/waiter/waiter.c new file mode 100644 index 0000000..575d631 --- /dev/null +++ b/userland/testbin/waiter/waiter.c @@ -0,0 +1,29 @@ +/* + * waiter.c + * + * Just sits there without doing anything. We use the read system call just to + * provide a way to wait. Intended to test our ability to detect stuck + * processes in userspace. + */ + +#include +#include + +int +main(void) +{ + char ch=0; + int len; + + while (ch!='q') { + len = read(STDIN_FILENO, &ch, 1); + if (len < 0) { + err(1, "stdin: read"); + } + if (len==0) { + /* EOF */ + break; + } + } + return 0; +}