/* $NetBSD$ */ /*- * Copyright (c) 2011 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Cherry G. Mathew * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ #include __RCSID("$NetBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Barrier stuff */ /* We use a semaphore for barriers because it works for fork(2) as * well as pthread(3) */ struct barrier { char *semfile; /* File name of the semaphore */ sem_t *lock; /* operates as a mutex */ volatile size_t stile; }; static void vm_barrier_init(struct barrier *bt, size_t ncpus) { assert(bt != NULL); char _semfile[] = "/semXXXX", *semfile = _semfile; // semfile = mktemp(semfile); if (semfile == NULL) { fprintf(stderr, "Unable to get unique filename for semaphore\n"); abort(); } bt->lock = sem_open(semfile, O_CREAT, 0600, 1); if (bt->lock == SEM_FAILED) { fprintf(stderr, "Unable to open semaphore\n"); perror("sem_open():"); abort(); } bt->semfile = malloc(sizeof semfile); if (bt->semfile == NULL) { fprintf(stderr, "Unable to malloc() for filename\n"); sem_close(bt->lock); sem_unlink(semfile); abort(); } strncpy(bt->semfile, _semfile, sizeof _semfile); bt->stile = ncpus; } static void vm_barrier_destroy(struct barrier *bt) { assert(bt != NULL); bt->stile = 0; sem_close(bt->lock); sem_unlink(bt->semfile); free(bt->semfile); } static void vm_barrier_hold(struct barrier *bt) { size_t stile; assert(bt != NULL); assert(bt->lock != NULL); assert(bt->stile != 0); printf("a) stile == %zd\n", bt->stile); while (sem_trywait(bt->lock)) assert(errno == EAGAIN); /* spinwait */ bt->stile--; sem_post(bt->lock); printf("b) stile == %zd\n", bt->stile); do { while (sem_trywait(bt->lock)) /* spinwait */ assert(errno == EAGAIN); stile = bt->stile; sem_post(bt->lock); } while (stile); printf("c) stile == %zd\n", stile); } /* * The goal of these tests is to stress the kernel pmap * implementation, from userspace. */ /* * Thread thrash: This test fires off one thread per CPU. * Each thread makes synchronised, interleaved data accesses to a * shared, locked page of memory. Each thread has a unique mapping to * this page, obtained via mmap(). The mappings to the shared page are * torn down and created afresh every time, in order to exercise the * pmap routines. (XXX: break down this test into smaller tests that * exercise identifiable areas of pmap.) * * This test only operates on a single pmap. */ static void thrash(void *arg) { assert(arg != NULL); int tid = *(int *)arg; /* Bind threads to given cpu */ printf("I am thread #%d\n", tid); return; } static cpuid_t getcpus(void) { int ncpu; static int mib[2] = { CTL_HW, HW_NCPU }; size_t len; len = sizeof ncpu; if (sysctl(mib, __arraycount(mib), &ncpu, &len, NULL, 0) == -1) { return 0; } return ncpu; } /* * Set the fault handler of the current process to fault_routine() * To restore the default handler, use prep_fault(NULL); * Returns the fault handler that has been set. */ static void * prep_fault(void *fault_routine) { return NULL; /* XXX: */ } /* Thread wrappers */ /* Quick note on thread "id". Since we assume one thread per cpu, the * cpuid is used in place of the thread "id" for all practical * purposes. */ static jmp_buf sequel[MAXCPUS]; /* Fault handler, to test for legitimate page faults. */ static void thread_pagefault(void) { longjmp(sequel[0 /* XXX */], 1); fprintf(stderr, "pagefault handler did not longjmp() !"); abort(); } struct thread_arg { void (*func)(void *); void (*abortf)(void *); void *arg; }; struct thread_ctx { cpuid_t cid; /* The cpu number we are running on */ pthread_t pth; cpuset_t *cset; struct barrier init_bar; struct thread_arg ctx; }; /* Can only be called from own thread */ static void thread_exit(struct thread_ctx *t) { assert(t != NULL); assert(pthread_equal(t->pth, pthread_self())); vm_barrier_destroy(&t->init_bar); cpuset_destroy(t->cset); free(t); pthread_exit(NULL); } static inline bool thread_equal(struct thread_ctx *t1, struct thread_ctx *t2) { return (t1 == t2); } /* * Same as thread_exit, but calls abort callback, if registered, * before exiting */ static void thread_abort(struct thread_ctx *t) { assert(t != NULL); assert(pthread_equal(t->pth, pthread_self())); if (t->ctx.abortf != NULL) { t->ctx.abortf(t->ctx.arg); } vm_barrier_destroy(&t->init_bar); cpuset_destroy(t->cset); free(t); pthread_exit(NULL); } static void * setjmp_tramp(void *arg) { assert(arg != NULL); pthread_t pth; struct thread_ctx *t = arg; pth = pthread_self(); printf("child addr of t->pth == %p\n", &t->pth); printf("child cid == %zd\n", t->cid); printf("child pth == %p\n", t->pth); sleep(1); vm_barrier_hold(&t->init_bar); /* Sync with thread_spawn */ printf("child pth after == %p\n", t->pth); printf("child pth self after == %p\n", pth); if (!pthread_equal(pth, t->pth)) { printf("not the right child\n"); while(1); } if (setjmp(sequel[t->cid])) { /* * got here via longjmp() from fault * routine. */ printf("caught exception\n"); prep_fault(NULL); /* XXX: reset exception handler */ thread_abort(t); } t->ctx.func(t->ctx.arg); thread_exit(t); return NULL; } static struct thread_ctx * thread_spawn(cpuid_t cid, /* cpu number */ void (*func)(void *), void *arg, void (*abortf)(void *)) { struct thread_ctx *t; cpuset_t *cpuset; assert(func != NULL); assert(cid <= MAXCPUS); t = (struct thread_ctx *) malloc(sizeof *t); if (t == NULL) { return NULL; } cpuset = cpuset_create(); if (cpuset == NULL) { printf("Could not create cpuset\n"); free(t); return NULL; } if (cpuset_set(cid, cpuset) == -1) { printf("Could not set cpuset affinity to cpu%lu \n", cid); cpuset_destroy(cpuset); free(t); return NULL; } t->cset = cpuset; t->cid = cid; t->ctx.func = func; t->ctx.arg = arg; if (abortf != NULL) { t->ctx.abortf = abortf; } vm_barrier_init(&t->init_bar, 2); printf("creating new thread for func: %p\n", t->ctx.func); printf("addr of t->pth == %p\n", &t->pth); if (pthread_create(&t->pth, NULL, setjmp_tramp, t)) { printf("error creating thread \n"); free(t); return NULL; } printf("parent cid == %zd\n", t->cid); printf("parent pth == %p\n", t->pth); vm_barrier_hold(&t->init_bar); /* Sync with setjmp_tramp() */ /* Set affinity */ if (pthread_setaffinity_np(t->pth, cpuset_size(t->cset), t->cset)) { printf("error binding thread to CPU %lu\n", t->cid); /* XXX: "destroy" the thread */ free(t); return NULL; } return t; } /* * This function reaps the context memory, not thread_wait(); * This makes it compulsory to use this function from the controlling * thread, to make sure memory is not leaked. * * This is slightly lame, but we're a testing framework, not a * threading library. */ static int thread_wait(struct thread_ctx *ctx) { int error; pthread_t pth; assert(ctx != NULL); pth = ctx->pth; error = pthread_join(pth, NULL); /* ctx is free()d by the thread on thread_exit() */ return error; } struct tt { int tid; pthread_t pth; }; static void * thread(void *arg) { struct tt *ttp = arg; int tid = ttp->tid; pthread_t pth = ttp->pth; printf("I am thread %d\n", tid); if (pthread_equal(pthread_self(), pth)) { printf("pthread_self() matches\n"); } sleep(3); pthread_exit(NULL); } static struct tt * spawn(void) { static int tid = 0; struct tt *ttp; printf("spawn entered at tid == %d\n", tid); ttp = malloc(sizeof *ttp); if (ttp == NULL) { fprintf(stderr, "malloc() failed\n"); abort(); } ttp->tid = tid; pthread_create(&ttp->pth, NULL, thread, &ttp->tid); printf("spawn finished at tid == %d\n", tid); tid++; return ttp; } ATF_TC(test_thread); ATF_TC_HEAD(test_thread, tc) { atf_tc_set_md_var(tc, "descr", "test pthreads"); } ATF_TC_BODY(test_thread, tc) { pthread_join(spawn()->pth, NULL); pthread_join(spawn()->pth, NULL); } ATF_TC(thread_thrash); ATF_TC_HEAD(thread_thrash, tc) { atf_tc_set_md_var(tc, "descr", "Thrash the TLB from within a single Address Space"); } ATF_TC_BODY(thread_thrash, tc) { cpuid_t cpuno, i; struct thread_ctx *t[MAXCPUS]; /* 1) Detect no. of cpus via cpuset(3) */ cpuno = getcpus(); printf("Detected %lu cpus\n", cpuno); ATF_REQUIRE(cpuno > 0); /* 2) Fire off threads */ printf("new threads\n"); (void) thread_pagefault; for (i = 0; i < cpuno; i++) { t[i] = thread_spawn(i, thrash, NULL, NULL); if (t[i] == NULL) { printf("thread spawn failed for cpu%lu\n", i); } /* XXX: destroy the other threads ? */ ATF_REQUIRE(t[i] != NULL); } /* Wait for threads to join */ for (i = 0; i < cpuno; i++) { thread_wait(t[i]); printf("joined\n"); } } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, test_thread); ATF_TP_ADD_TC(tp, thread_thrash); return atf_no_error(); }