sched_tests.c   [plain text]


/*
 *  sched_tests.c
 *  xnu_quick_test
 *
 *  Copyright 2011 Apple Inc. All rights reserved.
 *
 */

#include "tests.h"
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <mach/semaphore.h>
#include <unistd.h>
#include <err.h>
#include <sys/param.h>
#include <pthread.h>

#define DEBUG 0

#if DEBUG
#define dprintf(...) printf(__VA_ARGS__)
#else
#define dprintf(...) do { } while(0)
#endif

static uint64_t
nanos_to_abs(uint64_t ns, uint32_t numer, uint32_t denom)
{
	return (uint64_t)(ns * (((double)denom) / ((double)numer)));
}

static void set_realtime(void) {
	struct mach_timebase_info mti;
	thread_time_constraint_policy_data_t pol;
	kern_return_t kret;

	kret = mach_timebase_info(&mti);
	if (kret != KERN_SUCCESS) {
		warnx("Could not get timebase info %d", kret);
		return;
	}

	/* 1s 100ms 10ms */
	pol.period      = nanos_to_abs(1000000000, mti.numer, mti.denom);
	pol.constraint  = nanos_to_abs(100000000,  mti.numer, mti.denom);
	pol.computation = nanos_to_abs(10000000,   mti.numer, mti.denom);
	pol.preemptible = 0; /* Ignored by OS */

	kret = thread_policy_set(mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t) &pol, THREAD_TIME_CONSTRAINT_POLICY_COUNT);
	if (kret != KERN_SUCCESS) {
		warnx("Failed to set realtime %d", kret);
	}
}

struct t1_ctx {
	pthread_t __p;
	int currentThread;
	int totalThreads;
	boolean_t useRealtime;
	semaphore_t wait_to_start;
	semaphore_t next_waiter;

	semaphore_t common_sema; /* main thing everyone blocks on */
	uint64_t wakeup_time; /* out parameter */
};

void *t1(void *arg) {
	struct t1_ctx *ctx = (struct t1_ctx *)arg;
	kern_return_t kret;

	dprintf("thread %d (pthread %p) started\n", ctx->currentThread, pthread_self());

	/* Wait to allow previous thread to block on common semaphore */
	kret = semaphore_wait(ctx->wait_to_start);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_wait(wait_to_start) thread %d failed %d",
			  ctx->currentThread, kret);
	}

	sleep(1);

	if (ctx->useRealtime) {
		dprintf("thread %d going realtime\n", ctx->currentThread);
		set_realtime();
	}

	kret = semaphore_signal(ctx->next_waiter);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_signal(next_waiter) thread %d failed %d",
			  ctx->currentThread, kret);
	}

	/*
	 * We have 1 second to block on the common semaphore before
	 * the next thread does.
	 */
	dprintf("thread %d blocking on common semaphore\n", ctx->currentThread);

	kret = semaphore_wait(ctx->common_sema);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_wait(common_sema) thread %d failed %d",
			  ctx->currentThread, kret);
	}

	/* Save our time for analysis */
	ctx->wakeup_time = mach_absolute_time();
	dprintf("thread %d woke up at %llu\n", ctx->currentThread, ctx->wakeup_time);

	kret = semaphore_signal(ctx->common_sema);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_signal(common_sema) thread %d failed %d",
			  ctx->currentThread, kret);
	}

	return NULL;
}
	   



int sched_tests( void * the_argp )
{
	kern_return_t kret;
	int ret;
	int i;
	semaphore_t common_sema;
	semaphore_t all_checked_in;
	
	struct t1_ctx ctxs[3];
	
	/*
	 * Test 8979062. Ensure that a realtime thread that
	 * blocks on a semaphore after a non-realtime thread
	 * gets woken up first.
	 */

	kret = semaphore_create(mach_task_self(), &common_sema, SYNC_POLICY_FIFO /* not really, in this case */, 0);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_create failed: %d", kret);
		return -1;
	}

	kret = semaphore_create(mach_task_self(), &all_checked_in, SYNC_POLICY_FIFO, 0);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_create failed: %d", kret);
		return -1;
	}

	memset(&ctxs, 0x00, sizeof(ctxs));
	for (i=0; i < sizeof(ctxs)/sizeof(ctxs[0]); i++) {
		ctxs[i].__p = NULL; /* set later */
		ctxs[i].currentThread = i;
		ctxs[i].totalThreads = sizeof(ctxs)/sizeof(ctxs[0]);
		ctxs[i].useRealtime = FALSE;

		kret = semaphore_create(mach_task_self(), &ctxs[i].wait_to_start, SYNC_POLICY_FIFO /* not really, in this case */, 0);
		if (kret != KERN_SUCCESS) {
			warnx("semaphore_create failed: %d", kret);
			return -1;
		}
		ctxs[i].next_waiter = MACH_PORT_NULL; /* set later */
		ctxs[i].common_sema = common_sema;
		ctxs[i].wakeup_time = 0;
	}

	ctxs[1].useRealtime = TRUE;

	for (i=1; i < sizeof(ctxs)/sizeof(ctxs[0]); i++) {
		ctxs[i-1].next_waiter = ctxs[i].wait_to_start;
	}
	ctxs[i-1].next_waiter = all_checked_in;


	for (i=0; i < sizeof(ctxs)/sizeof(ctxs[0]); i++) {
		ret = pthread_create(&ctxs[i].__p, NULL, t1, &ctxs[i]);
		if (ret != 0) {
			warn("pthread_create failed");
			return -1;
		}
	}

	/* wake up first thread */
	kret = semaphore_signal(ctxs[0].wait_to_start);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_signal(initial wait_to_start) failed %d", kret);
		return -1;
	}

	/* Wait for everyone to have blocked */
	kret = semaphore_wait(all_checked_in);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_wait(all_checked_in) failed %d", kret);
		return -1;
	}

	/* Give some slack for last guy */
	sleep(1);

	kret = semaphore_signal(common_sema);
	if (kret != KERN_SUCCESS) {
		warnx("semaphore_signal(initial common_sema) failed %d", kret);
		return -1;
	}

	for (i=0; i < sizeof(ctxs)/sizeof(ctxs[0]); i++) {
		ret = pthread_join(ctxs[i].__p, NULL);
		if (ret != 0) {
			warn("pthread_join failed");
			return -1;
		}
	}

	dprintf("All threads joined\n");

	/*
	 * Our expectation is that thread 1 was realtime and
	 * finished first, followed by 0 and then 2
	 */
	if ((ctxs[1].wakeup_time < ctxs[0].wakeup_time)
		&& (ctxs[0].wakeup_time < ctxs[2].wakeup_time)) {
		/* success */
	} else {
		warnx("Threads woken out of order %llu %llu %llu",
			  ctxs[0].wakeup_time, ctxs[1].wakeup_time,
			  ctxs[2].wakeup_time);
		return -1;
	}

	return 0;
}