os161/kern/test/semunit.c
2015-12-23 00:50:04 +00:00

821 lines
17 KiB
C

/*
* Copyright (c) 2015
* 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.
*/
#include <types.h>
#include <lib.h>
#include <spinlock.h>
#include <synch.h>
#include <thread.h>
#include <current.h>
#include <clock.h>
#include <test.h>
/*
* Unit tests for semaphores.
*
* We test 21 correctness criteria, each stated in a comment at the
* top of each test.
*
* Note that these tests go inside the semaphore abstraction to
* validate the internal state.
*
* All tests (apart from those that crash) attempt to clean up after
* running, to avoid leaking memory and leaving extra threads lying
* around. Tests with a cleanup phase call ok() before going to it in
* case the cleanup crashes -- this should not happen of course but if
* it does it should be distinguished from the main part of the test
* itself dying.
*/
#define NAMESTRING "some-silly-name"
////////////////////////////////////////////////////////////
// support code
static unsigned waiters_running = 0;
static struct spinlock waiters_lock = SPINLOCK_INITIALIZER;
static
void
ok(void)
{
kprintf("Test passed; now cleaning up.\n");
}
/*
* Wrapper for sem_create when we aren't explicitly tweaking it.
*/
static
struct semaphore *
makesem(unsigned count)
{
struct semaphore *sem;
sem = sem_create(NAMESTRING, count);
if (sem == NULL) {
panic("semunit: whoops: sem_create failed\n");
}
return sem;
}
/*
* A thread that just waits on a semaphore.
*/
static
void
waiter(void *vsem, unsigned long junk)
{
struct semaphore *sem = vsem;
(void)junk;
P(sem);
spinlock_acquire(&waiters_lock);
KASSERT(waiters_running > 0);
waiters_running--;
spinlock_release(&waiters_lock);
}
/*
* Set up a waiter.
*/
static
void
makewaiter(struct semaphore *sem)
{
int result;
spinlock_acquire(&waiters_lock);
waiters_running++;
spinlock_release(&waiters_lock);
result = thread_fork("semunit waiter", NULL, waiter, sem, 0);
if (result) {
panic("semunit: thread_fork failed\n");
}
kprintf("Sleeping for waiter to run\n");
clocksleep(1);
}
/*
* Spinlocks don't natively provide this, because it only makes sense
* under controlled conditions.
*
* Note that we should really read the holder atomically; but because
* we're using this under controlled conditions, it doesn't actually
* matter -- nobody is supposed to be able to touch the holder while
* we're checking it, or the check wouldn't be reliable; and, provided
* clocksleep works, nobody can.
*/
static
bool
spinlock_not_held(struct spinlock *splk)
{
return splk->splk_holder == NULL;
}
////////////////////////////////////////////////////////////
// tests
/*
* 1. After a successful sem_create:
* - sem_name compares equal to the passed-in name
* - sem_name is not the same pointer as the passed-in name
* - sem_wchan is not null
* - sem_lock is not held and has no owner
* - sem_count is the passed-in count
*/
int
semu1(int nargs, char **args)
{
struct semaphore *sem;
const char *name = NAMESTRING;
(void)nargs; (void)args;
sem = sem_create(name, 56);
if (sem == NULL) {
panic("semu1: whoops: sem_create failed\n");
}
KASSERT(!strcmp(sem->sem_name, name));
KASSERT(sem->sem_name != name);
KASSERT(sem->sem_wchan != NULL);
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 56);
ok();
/* clean up */
sem_destroy(sem);
return 0;
}
/*
* 2. Passing a null name to sem_create asserts or crashes.
*/
int
semu2(int nargs, char **args)
{
struct semaphore *sem;
(void)nargs; (void)args;
kprintf("This should crash with a kernel null dereference\n");
sem = sem_create(NULL, 44);
(void)sem;
panic("semu2: sem_create accepted a null name\n");
return 0;
}
/*
* 3. Passing a null semaphore to sem_destroy asserts or crashes.
*/
int
semu3(int nargs, char **args)
{
(void)nargs; (void)args;
kprintf("This should assert that sem != NULL\n");
sem_destroy(NULL);
panic("semu3: sem_destroy accepted a null semaphore\n");
}
/*
* 4. sem_count is an unsigned type.
*/
int
semu4(int nargs, char **args)
{
struct semaphore *sem;
(void)nargs; (void)args;
/* Create a semaphore with count 0. */
sem = makesem(0);
/* Decrement the count. */
sem->sem_count--;
/* This value should be positive. */
KASSERT(sem->sem_count > 0);
/* Clean up. */
ok();
sem_destroy(sem);
return 0;
}
/*
* 5. A semaphore can be successfully initialized with a count of at
* least 0xf0000000.
*/
int
semu5(int nargs, char **args)
{
struct semaphore *sem;
(void)nargs; (void)args;
sem = sem_create(NAMESTRING, 0xf0000000U);
if (sem == NULL) {
/* This might not be an innocuous malloc shortage. */
panic("semu5: sem_create failed\n");
}
KASSERT(sem->sem_count == 0xf0000000U);
/* Clean up. */
ok();
sem_destroy(sem);
return 0;
}
/*
* 6. Passing a semaphore with a waiting thread to sem_destroy asserts
* (in the wchan code).
*/
int
semu6(int nargs, char **args)
{
struct semaphore *sem;
(void)nargs; (void)args;
sem = makesem(0);
makewaiter(sem);
kprintf("This should assert that the wchan's threadlist is empty\n");
sem_destroy(sem);
panic("semu6: wchan_destroy with waiters succeeded\n");
return 0;
}
/*
* 7. Calling V on a semaphore does not block the caller, regardless
* of the semaphore count.
*/
int
semu7(int nargs, char **args)
{
struct semaphore *sem;
struct spinlock lk;
(void)nargs; (void)args;
sem = makesem(0);
/*
* Check for blocking by taking a spinlock; if we block while
* holding a spinlock, wchan_sleep will assert.
*/
spinlock_init(&lk);
spinlock_acquire(&lk);
/* try with count 0, count 1, and count 2, just for completeness */
V(sem);
V(sem);
V(sem);
/* Clean up. */
ok();
spinlock_release(&lk);
spinlock_cleanup(&lk);
sem_destroy(sem);
return 0;
}
/*
* 8/9. After calling V on a semaphore with no threads waiting:
* - sem_name is unchanged
* - sem_wchan is unchanged
* - sem_lock is (still) unheld and has no owner
* - sem_count is increased by one
*
* This is true even if we are in an interrupt handler.
*/
static
void
do_semu89(bool interrupthandler)
{
struct semaphore *sem;
struct wchan *wchan;
const char *name;
sem = makesem(0);
/* check preconditions */
name = sem->sem_name;
wchan = sem->sem_wchan;
KASSERT(!strcmp(name, NAMESTRING));
KASSERT(spinlock_not_held(&sem->sem_lock));
/*
* The right way to this is to set up an actual interrupt,
* e.g. an interprocessor interrupt, and hook onto it to run
* the V() in the actual interrupt handler. However, that
* requires a good bit of infrastructure that we don't
* have. Instead we'll fake it by explicitly setting
* curthread->t_in_interrupt.
*/
if (interrupthandler) {
KASSERT(curthread->t_in_interrupt == false);
curthread->t_in_interrupt = true;
}
V(sem);
if (interrupthandler) {
KASSERT(curthread->t_in_interrupt == true);
curthread->t_in_interrupt = false;
}
/* check postconditions */
KASSERT(name == sem->sem_name);
KASSERT(!strcmp(name, NAMESTRING));
KASSERT(wchan == sem->sem_wchan);
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 1);
/* clean up */
ok();
sem_destroy(sem);
}
int
semu8(int nargs, char **args)
{
(void)nargs; (void)args;
do_semu89(false /*interrupthandler*/);
return 0;
}
int
semu9(int nargs, char **args)
{
(void)nargs; (void)args;
do_semu89(true /*interrupthandler*/);
return 0;
}
/*
* 10/11. After calling V on a semaphore with one thread waiting, and giving
* it time to run:
* - sem_name is unchanged
* - sem_wchan is unchanged
* - sem_lock is (still) unheld and has no owner
* - sem_count is still 0
* - the other thread does in fact run
*
* This is true even if we are in an interrupt handler.
*/
static
int
do_semu1011(bool interrupthandler)
{
struct semaphore *sem;
struct wchan *wchan;
const char *name;
sem = makesem(0);
makewaiter(sem);
/* check preconditions */
name = sem->sem_name;
wchan = sem->sem_wchan;
KASSERT(!strcmp(name, NAMESTRING));
KASSERT(spinlock_not_held(&sem->sem_lock));
spinlock_acquire(&waiters_lock);
KASSERT(waiters_running == 1);
spinlock_release(&waiters_lock);
/* see above */
if (interrupthandler) {
KASSERT(curthread->t_in_interrupt == false);
curthread->t_in_interrupt = true;
}
V(sem);
if (interrupthandler) {
KASSERT(curthread->t_in_interrupt == true);
curthread->t_in_interrupt = false;
}
/* give the waiter time to exit */
clocksleep(1);
/* check postconditions */
KASSERT(name == sem->sem_name);
KASSERT(!strcmp(name, NAMESTRING));
KASSERT(wchan == sem->sem_wchan);
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 0);
spinlock_acquire(&waiters_lock);
KASSERT(waiters_running == 0);
spinlock_release(&waiters_lock);
/* clean up */
ok();
sem_destroy(sem);
return 0;
}
int
semu10(int nargs, char **args)
{
(void)nargs; (void)args;
do_semu1011(false /*interrupthandler*/);
return 0;
}
int
semu11(int nargs, char **args)
{
(void)nargs; (void)args;
do_semu1011(true /*interrupthandler*/);
return 0;
}
/*
* 12/13. After calling V on a semaphore with two threads waiting, and
* giving it time to run:
* - sem_name is unchanged
* - sem_wchan is unchanged
* - sem_lock is (still) unheld and has no owner
* - sem_count is still 0
* - one of the other threads does in fact run
* - the other one does not
*/
static
void
semu1213(bool interrupthandler)
{
struct semaphore *sem;
struct wchan *wchan;
const char *name;
sem = makesem(0);
makewaiter(sem);
makewaiter(sem);
/* check preconditions */
name = sem->sem_name;
wchan = sem->sem_wchan;
KASSERT(!strcmp(name, NAMESTRING));
wchan = sem->sem_wchan;
KASSERT(spinlock_not_held(&sem->sem_lock));
spinlock_acquire(&waiters_lock);
KASSERT(waiters_running == 2);
spinlock_release(&waiters_lock);
/* see above */
if (interrupthandler) {
KASSERT(curthread->t_in_interrupt == false);
curthread->t_in_interrupt = true;
}
V(sem);
if (interrupthandler) {
KASSERT(curthread->t_in_interrupt == true);
curthread->t_in_interrupt = false;
}
/* give the waiter time to exit */
clocksleep(1);
/* check postconditions */
KASSERT(name == sem->sem_name);
KASSERT(!strcmp(name, NAMESTRING));
KASSERT(wchan == sem->sem_wchan);
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 0);
spinlock_acquire(&waiters_lock);
KASSERT(waiters_running == 1);
spinlock_release(&waiters_lock);
/* clean up */
ok();
V(sem);
clocksleep(1);
spinlock_acquire(&waiters_lock);
KASSERT(waiters_running == 0);
spinlock_release(&waiters_lock);
sem_destroy(sem);
}
int
semu12(int nargs, char **args)
{
(void)nargs; (void)args;
semu1213(false /*interrupthandler*/);
return 0;
}
int
semu13(int nargs, char **args)
{
(void)nargs; (void)args;
semu1213(true /*interrupthandler*/);
return 0;
}
/*
* 14. Calling V on a semaphore whose count is the maximum allowed value
* asserts.
*/
int
semu14(int nargs, char **args)
{
struct semaphore *sem;
(void)nargs; (void)args;
kprintf("This should assert that sem_count is > 0.\n");
sem = makesem(0);
/*
* The maximum value is (unsigned)-1. Get this by decrementing
* from 0.
*/
sem->sem_count--;
V(sem);
KASSERT(sem->sem_count == 0);
panic("semu14: V tolerated count wraparound\n");
return 0;
}
/*
* 15. Calling V on a null semaphore asserts.
*/
int
semu15(int nargs, char **args)
{
(void)nargs; (void)args;
kprintf("This should assert that the semaphore isn't null.\n");
V(NULL);
panic("semu15: V tolerated null semaphore\n");
return 0;
}
/*
* 16. Calling P on a semaphore with count > 0 does not block the caller.
*/
int
semu16(int nargs, char **args)
{
struct semaphore *sem;
struct spinlock lk;
(void)nargs; (void)args;
sem = makesem(1);
/* As above, check for improper blocking by taking a spinlock. */
spinlock_init(&lk);
spinlock_acquire(&lk);
P(sem);
ok();
spinlock_release(&lk);
spinlock_cleanup(&lk);
sem_destroy(sem);
return 0;
}
/*
* 17. Calling P on a semaphore with count == 0 does block the caller.
*/
static struct thread *semu17_thread;
static
void
semu17_sub(void *semv, unsigned long junk)
{
struct semaphore *sem = semv;
(void)junk;
semu17_thread = curthread;
/* precondition */
KASSERT(sem->sem_count == 0);
P(sem);
}
int
semu17(int nargs, char **args)
{
struct semaphore *sem;
int result;
(void)nargs; (void)args;
semu17_thread = NULL;
sem = makesem(0);
result = thread_fork("semu17_sub", NULL, semu17_sub, sem, 0);
if (result) {
panic("semu17: whoops: thread_fork failed\n");
}
kprintf("Waiting for subthread...\n");
clocksleep(1);
/* The subthread should be blocked. */
KASSERT(semu17_thread != NULL);
KASSERT(semu17_thread->t_state == S_SLEEP);
/* Clean up. */
ok();
V(sem);
clocksleep(1);
sem_destroy(sem);
semu17_thread = NULL;
return 0;
}
/*
* 18. After calling P on a semaphore with count > 0:
* - sem_name is unchanged
* - sem_wchan is unchanged
* - sem_lock is unheld and has no owner
* - sem_count is one less
*/
int
semu18(int nargs, char **args)
{
struct semaphore *sem;
struct wchan *wchan;
const char *name;
(void)nargs; (void)args;
sem = makesem(1);
/* preconditions */
name = sem->sem_name;
KASSERT(!strcmp(name, NAMESTRING));
wchan = sem->sem_wchan;
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 1);
P(sem);
/* postconditions */
KASSERT(name == sem->sem_name);
KASSERT(!strcmp(name, NAMESTRING));
KASSERT(wchan == sem->sem_wchan);
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 0);
return 0;
}
/*
* 19. After calling P on a semaphore with count == 0 and another
* thread uses V exactly once to cause a wakeup:
* - sem_name is unchanged
* - sem_wchan is unchanged
* - sem_lock is unheld and has no owner
* - sem_count is still 0
*/
static
void
semu19_sub(void *semv, unsigned long junk)
{
struct semaphore *sem = semv;
(void)junk;
kprintf("semu19: waiting for parent to sleep\n");
clocksleep(1);
/*
* We could assert here that the parent *is* sleeping; but for
* that we'd need its thread pointer and it's not worth the
* trouble.
*/
V(sem);
}
int
semu19(int nargs, char **args)
{
struct semaphore *sem;
struct wchan *wchan;
const char *name;
int result;
(void)nargs; (void)args;
sem = makesem(0);
result = thread_fork("semu19_sub", NULL, semu19_sub, sem, 0);
if (result) {
panic("semu19: whoops: thread_fork failed\n");
}
/* preconditions */
name = sem->sem_name;
KASSERT(!strcmp(name, NAMESTRING));
wchan = sem->sem_wchan;
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 0);
P(sem);
/* postconditions */
KASSERT(name == sem->sem_name);
KASSERT(!strcmp(name, NAMESTRING));
KASSERT(wchan == sem->sem_wchan);
KASSERT(spinlock_not_held(&sem->sem_lock));
KASSERT(sem->sem_count == 0);
return 0;
}
/*
* 20/21. Calling P in an interrupt handler asserts, regardless of the
* count.
*/
int
semu20(int nargs, char **args)
{
struct semaphore *sem;
(void)nargs; (void)args;
kprintf("This should assert that we aren't in an interrupt\n");
sem = makesem(0);
/* as above */
curthread->t_in_interrupt = true;
P(sem);
panic("semu20: P tolerated being in an interrupt handler\n");
return 0;
}
int
semu21(int nargs, char **args)
{
struct semaphore *sem;
(void)nargs; (void)args;
kprintf("This should assert that we aren't in an interrupt\n");
sem = makesem(1);
/* as above */
curthread->t_in_interrupt = true;
P(sem);
panic("semu21: P tolerated being in an interrupt handler\n");
return 0;
}
/*
* 22. Calling P on a null semaphore asserts.
*/
int
semu22(int nargs, char **args)
{
(void)nargs; (void)args;
kprintf("This should assert that the semaphore isn't null.\n");
P(NULL);
panic("semu22: P tolerated null semaphore\n");
return 0;
}