/****************************************************************************** * * Copyright © International Business Machines Corp., 2006, 2008 * * 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; either version 2 of the License, or * (at your option) any later version. * * 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 have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * NAME * prio-preempt.c * * DESCRIPTION * Test whether priority pre-emption works fine. * * The main thread: * - Creates a minimum of (N-1) busy threads at priority starting at * SCHED_FIFO + 80 * - Creates 26 FIFO (T1, T2,...,T26) threads with priorities 10, 11,...,36. * - Each of these worker threads executes the following piece of code: * pthread_mutex_lock(Mi); * pthread_cond_wait(CVi); * pthread_mutex_unlock(Mi); * * where Mi is the ith pthread_mutex_t and CVi is the ith conditional * variable.So, at the end of this loop, 26 threads are all waiting on * seperate condvars and mutexes. * - Wakes up thread at priority 10 (T1) by executing: * pthread_mutex_lock(M1); * pthread_cond_signal(CV1); * pthread_mutex_unlock(M1); * * - Waits for all the worker threads to finish execution. * T1 then wakes up T2 by signalling on the condvar CV2 and sets a flag * called T1_after_wait to indicate that it is after the wait. It then * checks if T2_after_wait has been set or not. If not, the test fails, * else the process continues with other threads. The thread T1 expects * T2_after_wait to be set as, the moment T1 signals on CV2, T2 is * supposed to be scheduled (in accordance with priority preemption). * * USAGE: * Use run_auto.sh script in current directory to build and run test. * * AUTHOR * Dinakar Guniguntala * * HISTORY * 2006-Jun-01: Initial version by Dinakar Guniguntala * Changes from John Stultz and Vivek Pallantla * *****************************************************************************/ #include #include #include #include #include #include #include #include #include #define NUM_WORKERS 27 #define CHECK_LIMIT 1 volatile int busy_threads = 0; volatile int test_over = 0; volatile int threads_running = 0; static int rt_threads = -1; static int int_threads = 0; static pthread_mutex_t bmutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutex[NUM_WORKERS + 1]; static pthread_cond_t cond[NUM_WORKERS + 1]; static int t_after_wait[NUM_WORKERS]; static int ret = 0; pthread_barrier_t barrier; void usage(void) { rt_help(); printf("prio-preempt specific options:\n"); printf(" -i #: enable interrupter threads\n"); printf(" -n# #: number of busy threads\n"); } int parse_args(int c, char *v) { int handled = 1; switch (c) { case 'h': usage(); exit(0); case 'i': int_threads = 1; break; case 'n': rt_threads = atoi(v); break; default: handled = 0; break; } return handled; } void *int_thread(void *arg) { intptr_t a = 0; while (!test_over) { /* do some busy work */ if (!(a % 4)) a = a * 3; else if (!(a % 6)) a = a / 2; else a++; usleep(20); } return (void *)a; } void *busy_thread(void *arg) { struct sched_param sched_param; int policy, mypri = 0, tid; tid = (intptr_t) (((struct thread *)arg)->arg); if (pthread_getschedparam(pthread_self(), &policy, &sched_param) != 0) { printf("ERR: Couldn't get pthread info \n"); } else { mypri = sched_param.sched_priority; } pthread_mutex_lock(&bmutex); busy_threads++; printf("Busy Thread %d(%d): Running...\n", tid, mypri); pthread_mutex_unlock(&bmutex); /* TODO: Add sched set affinity here */ /* Busy loop */ while (!test_over) ; printf("Busy Thread %d(%d): Exiting\n", tid, mypri); return NULL; } void *worker_thread(void *arg) { struct sched_param sched_param; int policy, rc, mypri = 0, tid, times = 0; tid = (intptr_t) (((struct thread *)arg)->arg); nsec_t pstart, pend; if (pthread_getschedparam(pthread_self(), &policy, &sched_param) != 0) { printf("ERR: Couldn't get pthread info \n"); } else { mypri = sched_param.sched_priority; } /* check in */ pthread_mutex_lock(&bmutex); threads_running++; pthread_mutex_unlock(&bmutex); /* block */ rc = pthread_mutex_lock(&mutex[tid]); if (tid == 0) pthread_barrier_wait(&barrier); rc = pthread_cond_wait(&cond[tid], &mutex[tid]); rc = pthread_mutex_unlock(&mutex[tid]); debug(DBG_INFO, "%llu: Thread %d(%d) wakes up from sleep \n", rt_gettime(), tid, mypri); /*check if we're the last thread */ if (tid == NUM_WORKERS - 1) { t_after_wait[tid] = 1; pthread_mutex_lock(&bmutex); threads_running--; pthread_mutex_unlock(&bmutex); return NULL; } /* Signal next thread */ rc = pthread_mutex_lock(&mutex[tid + 1]); rc = pthread_cond_signal(&cond[tid + 1]); debug(DBG_INFO, "%llu: Thread %d(%d): Sent signal (%d) to (%d)\n", rt_gettime(), tid, mypri, rc, tid + 1); pstart = pend = rt_gettime(); rc = pthread_mutex_unlock(&mutex[tid + 1]); debug(DBG_INFO, "%llu: Thread %d(%d) setting it's bit \n", rt_gettime(), tid, mypri); t_after_wait[tid] = 1; while (t_after_wait[tid + 1] != 1) { pend = rt_gettime(); times++; } if (times >= (int)pass_criteria) { printf ("Thread %d(%d): Non-Preempt limit reached. %llu ns latency\n", tid, mypri, pend - pstart); ret = 1; } /* check out */ pthread_mutex_lock(&bmutex); threads_running--; pthread_mutex_unlock(&bmutex); return NULL; } void *master_thread(void *arg) { int i, pri_boost; pthread_barrier_init(&barrier, NULL, 2); /* start interrupter thread */ if (int_threads) { pri_boost = 90; for (i = 0; i < rt_threads; i++) { create_fifo_thread(int_thread, NULL, sched_get_priority_min(SCHED_FIFO) + pri_boost); } } /* start the (N-1) busy threads */ pri_boost = 80; for (i = rt_threads; i > 1; i--) { create_fifo_thread(busy_thread, (void *)(intptr_t) i, sched_get_priority_min(SCHED_FIFO) + pri_boost); } /* make sure children are started */ while (busy_threads < (rt_threads - 1)) usleep(100); printf("Busy threads created!\n"); /* start NUM_WORKERS worker threads */ for (i = 0, pri_boost = 10; i < NUM_WORKERS; i++, pri_boost += 2) { pthread_mutex_init(&mutex[i], NULL); pthread_cond_init(&cond[i], NULL); create_fifo_thread(worker_thread, (void *)(intptr_t) i, sched_get_priority_min(SCHED_FIFO) + pri_boost); } printf("Worker threads created\n"); /* Let the worker threads wait on the cond vars */ while (threads_running < NUM_WORKERS) usleep(100); /* Ensure the first worker has called cond_wait */ pthread_barrier_wait(&barrier); printf("Signaling first thread\n"); pthread_mutex_lock(&mutex[0]); pthread_cond_signal(&cond[0]); pthread_mutex_unlock(&mutex[0]); while (threads_running) usleep(500000); /* this period greatly affects the number of failures! */ test_over = 1; return NULL; } int main(int argc, char *argv[]) { int pri_boost, numcpus; setup(); pass_criteria = CHECK_LIMIT; rt_init("hin:", parse_args, argc, argv); numcpus = sysconf(_SC_NPROCESSORS_ONLN); /* Max no. of busy threads should always be less than/equal the no. of cpus Otherwise, the box will hang */ if (rt_threads == -1 || rt_threads > numcpus) { rt_threads = numcpus; printf("Maximum busy thread count(%d), " "should not exceed number of cpus(%d)\n", rt_threads, numcpus); printf("Using %d\n", numcpus); } /* Test boilder plate: title and parameters */ printf("\n-------------------\n"); printf("Priority Preemption\n"); printf("-------------------\n\n"); printf("Busy Threads: %d\n", rt_threads); printf("Interrupter Threads: %s\n", int_threads ? "Enabled" : "Disabled"); printf("Worker Threads: %d\n\n", NUM_WORKERS); pri_boost = 81; create_fifo_thread(master_thread, NULL, sched_get_priority_min(SCHED_FIFO) + pri_boost); /* wait for threads to complete */ join_threads(); printf ("\nCriteria: All threads appropriately preempted within %d loop(s)\n", (int)pass_criteria); printf("Result: %s\n", ret ? "FAIL" : "PASS"); return ret; }