Mercurial > ecos
view packages/kernel/current/tests/stress_threads.c @ 8:ece80412419a ecos-sw-1999-05-21
Merge from eCos master repository on 1999-05-21-22:05:54-BST
| author | jlarmour |
|---|---|
| date | Fri, 21 May 1999 15:09:30 +0000 |
| parents | 1d7f19c9e4d1 |
| children | d3fbcdfa1b2f |
line wrap: on
line source
//========================================================================== // // stress_threads.cxx // // Basic thread stress test // //========================================================================== //####COPYRIGHTBEGIN#### // // ------------------------------------------- // The contents of this file are subject to the Cygnus eCos Public License // Version 1.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://sourceware.cygnus.com/ecos // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations under // the License. // // The Original Code is eCos - Embedded Cygnus Operating System, released // September 30, 1998. // // The Initial Developer of the Original Code is Cygnus. Portions created // by Cygnus are Copyright (C) 1998,1999 Cygnus Solutions. All Rights Reserved. // ------------------------------------------- // //####COPYRIGHTEND#### //========================================================================== //#####DESCRIPTIONBEGIN#### // // Author(s): rosalia // Contributors: rosalia, jskov // Date: 1999-04-13 // Description: Very simple thread stress test, with some memory // allocation and alarm handling. // // Notes: // If client_makes_request is big, it means that there are made many more // client requests than can be serviced. Consequently, clients are wasting // CPU time and should be sleeping more. // // The list of handler invocations show how many threads are running // at the same time. The more powerful the CPU, the more the numbers // should spread out. //####DESCRIPTIONEND#### #include <pkgconf/system.h> #include <cyg/infra/testcase.h> #include <cyg/hal/hal_arch.h> #if defined(CYGPKG_KERNEL) && defined(CYGPKG_IO) && defined(CYGPKG_LIBC) #include <pkgconf/kernel.h> #include <pkgconf/libc.h> #if defined(CYGFUN_KERNEL_API_C) #include <cyg/kernel/kapi.h> #ifdef CYGPKG_LIBC_STDIO #include <stdio.h> #include <stdlib.h> #if defined(CYGPKG_LIBM) #include <math.h> #include <assert.h> #if defined(CYGFUN_KERNEL_THREADS_TIMER) #if defined(CYGPKG_LIBC_MALLOC) /* if TIME_LIMIT is defined, it represents the number of seconds this test should last; if it is undefined the test will go forever */ #define DEATH_TIME_LIMIT 20 /* #undef DEATH_TIME_LIMIT */ #define STACK_SIZE (CYGNUM_HAL_STACK_SIZE_TYPICAL) #define STACK_SIZE2 (8*1024 + CYGNUM_HAL_STACK_SIZE_TYPICAL) /* Allocate priorities in this order. This ensures that handlers (which are the ones using the CPU) get enough CPU time to actually complete their tasks. */ #define N_MAIN 1 #define MAX_HANDLERS 19 #define N_LISTENERS 4 #define N_CLIENTS 4 #if (CYGNUM_KERNEL_SCHED_PRIORITIES >= (N_MAIN+MAX_HANDLERS+N_LISTENERS+N_CLIENTS)) /* if we use the bitmap scheduler we must make sure we don't use the same priority more than once, so we must store those already in use */ static volatile char priority_in_use[N_MAIN+MAX_HANDLERS+N_LISTENERS+N_CLIENTS]; /* now declare (and allocate space for) some kernel objects, like the threads we will use */ cyg_thread main_thread_s; cyg_thread handler_thread_s[MAX_HANDLERS]; cyg_thread listener_thread_s[N_LISTENERS]; cyg_thread client_thread_s[N_CLIENTS]; /* space for stacks for all threads */ char main_stack[STACK_SIZE]; char handler_stack[MAX_HANDLERS][STACK_SIZE2]; char listener_stack[N_LISTENERS][STACK_SIZE]; char client_stack[N_CLIENTS][STACK_SIZE]; /* now the handles for the threads */ cyg_handle_t mainH; cyg_handle_t handlerH[MAX_HANDLERS]; cyg_handle_t listenerH[N_LISTENERS]; cyg_handle_t clientH[N_CLIENTS]; /* and now variables for the procedure which is the thread */ cyg_thread_entry_t main_program, client_program, listener_program, handler_program; /* a few mutexes used in the code */ cyg_mutex_t client_request_lock, handler_slot_lock, statistics_print_lock, free_handler_lock; /* global variables with which the handler IDs and thread priorities to free are communicated from handlers to main_program. Access to these are protected by free_handler_lock. An id of -1 means the that the variables are empty. */ volatile int free_handler_pri = 0; volatile int free_handler_id = -1; /* a global variable with which the client and server coordinate */ int client_makes_request = 0; /* indicates that it's time to print out a report */ int time_to_report = 0; /*** now application-specific variables ***/ /* an array that stores whether the handler threads are in use */ int handler_thread_in_use[MAX_HANDLERS]; /***** statistics-gathering variables *****/ struct s_statistics { /* store the number of times each handler has been invoked */ unsigned long handler_invocation_histogram[MAX_HANDLERS]; /* store how many times malloc has been attempted and how many times it has failed */ unsigned long malloc_tries, malloc_failures; /* how many threads have been created */ unsigned long thread_creations, thread_exits; }; struct s_statistics statistics; /* some function prototypes; those with the sc_ prefix are "statistics-collecting" versions of the cyg_ primitives */ void sc_thread_create( cyg_addrword_t sched_info, /* scheduling info (eg pri) */ cyg_thread_entry_t *entry, /* entry point function */ cyg_addrword_t entry_data, /* entry data */ char *name, /* optional thread name */ void *stack_base, /* stack base, NULL = alloc */ cyg_ucount32 stack_size, /* stack size, 0 = default */ cyg_handle_t *handle, /* returned thread handle */ cyg_thread *thread /* put thread here */ ); int get_handler_slot(cyg_handle_t current_threadH); void perform_stressful_tasks(void); void permute_array(char a[], int size, int seed); void setup_death_alarm(cyg_addrword_t data, cyg_handle_t *deathHp, cyg_alarm *death_alarm_p, int *killed_p); void print_statistics(void); /* we need to declare the alarm handling function (which is defined below), so that we can pass it to cyg_alarm_initialize() */ cyg_alarm_t report_alarm_func, death_alarm_func; /* handle and alarm for the report alarm */ cyg_handle_t report_alarmH, counterH, system_clockH; cyg_alarm report_alarm; /* we install our own startup routine which sets up threads */ void cyg_user_start(void) { int i; CYG_TEST_INIT(); CYG_TEST_INFO("# Entering stress's cyg_user_start() function"); cyg_mutex_init(&client_request_lock); cyg_mutex_init(&statistics_print_lock); cyg_mutex_init(&free_handler_lock); /* initialize statistics */ memset(&statistics, 0, sizeof(statistics)); /* clear priority table */ for (i = 0; i < sizeof(priority_in_use); i++) priority_in_use[i] = 0; /* initialize main thread */ { char thread_name[] = "main"; sc_thread_create(0, main_program, (cyg_addrword_t) 0, thread_name, (void *) main_stack, STACK_SIZE, &mainH, &main_thread_s); priority_in_use[0]++; } /* initialize all handler threads to not be in use */ for (i = 0; i < MAX_HANDLERS; ++i) { handler_thread_in_use[i] = 0; } for (i = 0; i < N_LISTENERS; ++i) { int prio; char thread_name[20]; sprintf(thread_name, "listener-%02d", i); prio = N_MAIN + MAX_HANDLERS + i; sc_thread_create(prio, listener_program, (cyg_addrword_t) i, thread_name, (void *) listener_stack[i], STACK_SIZE, &listenerH[i], &listener_thread_s[i]); CYG_ASSERT(0 == priority_in_use[prio], "Priority already in use!"); priority_in_use[prio]++; } for (i = 0; i < N_CLIENTS; ++i) { int prio; char thread_name[20]; sprintf(thread_name, "client-%02d", i); prio = N_MAIN + MAX_HANDLERS + N_LISTENERS + i; sc_thread_create(prio, client_program, (cyg_addrword_t) i, thread_name, (void *) client_stack[i], STACK_SIZE, &(clientH[i]), &client_thread_s[i]); CYG_ASSERT(0 == priority_in_use[prio], "Priority already in use!"); priority_in_use[prio]++; } cyg_thread_resume(mainH); for (i = 0; i < N_CLIENTS; ++i) { cyg_thread_resume(clientH[i]); } for (i = 0; i < N_LISTENERS; ++i) { cyg_thread_resume(listenerH[i]); } /* set up the alarm which gives periodic wakeups to say "time to print a report */ system_clockH = cyg_real_time_clock(); cyg_clock_to_counter(system_clockH, &counterH); cyg_alarm_create(counterH, report_alarm_func, (cyg_addrword_t) 4000, &report_alarmH, &report_alarm); if (cyg_test_is_simulator) { cyg_alarm_initialize(report_alarmH, cyg_current_time()+200, 200); } else { cyg_alarm_initialize(report_alarmH, cyg_current_time()+300, 4000); } } /* main_program() -- frees resources and prints status. */ void main_program(cyg_addrword_t data) { #ifdef DEATH_TIME_LIMIT cyg_handle_t deathH; cyg_alarm death_alarm; int is_dead = 0; setup_death_alarm(0, &deathH, &death_alarm, &is_dead); #endif /* DEATH_TIME_LIMIT */ printf("# Starting main\n"); for (;;) { int handler_id = -1; int handler_pri = 0; cyg_mutex_lock(&free_handler_lock); { // If any handler has left its ID, copy the ID and // priority values to local variables, and free up the // global communication variables again. if (-1 != free_handler_id) { handler_id = free_handler_id; handler_pri = free_handler_pri; free_handler_id = -1; } } cyg_mutex_unlock(&free_handler_lock); if (-1 != handler_id) { // Free the handler resources. This is done outside of the // free_handler_lock to avoid deadlocks. cyg_mutex_lock(&handler_slot_lock); { CYG_ASSERT(1 == priority_in_use[handler_pri], "Priority not in use!"); CYG_ASSERT(1 == handler_thread_in_use[handler_id], "Handler not in use!"); handler_thread_in_use[handler_id]--; priority_in_use[handler_pri]--; // Finally delete the handler thread. This must be done in a // loop, waiting for the call to return true. If it returns // false, go to sleep for a bit, so the killed thread gets a // chance to run and complete its business. while (!cyg_thread_delete(handlerH[handler_id])) { cyg_thread_delay(2); } } cyg_mutex_unlock(&handler_slot_lock); } // Print status if time. if (time_to_report) { time_to_report = 0; print_statistics(); } #ifdef DEATH_TIME_LIMIT // Stop test if time. if (is_dead) { print_statistics(); CYG_TEST_PASS_FINISH("Kernel thread stress test OK"); } #endif /* DEATH_TIME_LIMIT */ cyg_thread_delay(3); } } /* client_program() -- an obnoxious client which makes a lot of requests */ void client_program(cyg_addrword_t data) { int delay; printf("# Starting client-%d\n", (int) data); system_clockH = cyg_real_time_clock(); cyg_clock_to_counter(system_clockH, &counterH); for (;;) { delay = (rand() % 20); /* now send a request to the server */ cyg_mutex_lock(&client_request_lock); { ++client_makes_request; /* printf("client_makes_request %d\n", client_makes_request); */ } cyg_mutex_unlock(&client_request_lock); cyg_thread_delay(10+delay); /* cyg_thread_delay(0); */ } } /* listener_program() -- listens for a request and spawns a handler to take care of the request */ void listener_program(cyg_addrword_t data) { /* int message = (int) data; */ int handler_slot; printf("# Beginning execution; thread data is %d\n", (int) data); for (;;) { int make_request = 0; cyg_mutex_lock(&client_request_lock); { if (client_makes_request > 0) { --client_makes_request; make_request = 1; } } cyg_mutex_unlock(&client_request_lock); if (make_request) { int prio; /* printf("just got a request from a client (count = %d)\n", */ /* client_makes_request); */ handler_slot = get_handler_slot(listenerH[(int) data]); prio = N_MAIN+handler_slot; CYG_ASSERT(0 == priority_in_use[prio], "Priority already in use!"); priority_in_use[prio]++; sc_thread_create(prio, handler_program, (cyg_addrword_t) handler_slot, "handler", (void *) handler_stack[handler_slot], STACK_SIZE2, &handlerH[handler_slot], &handler_thread_s[handler_slot]); cyg_thread_resume(handlerH[handler_slot]); ++statistics.handler_invocation_histogram[handler_slot]; } cyg_thread_delay(2 + (rand() % 10)); } } /* handler_program() -- is spawned to handle each incoming request */ void handler_program(cyg_addrword_t data) { /* here is where we perform specific stressful tasks */ perform_stressful_tasks(); cyg_thread_delay(4 + (int) (0.5*log(1.0 + fabs((rand() % 1000000))))); /* cyg_thread_delay(0); */ ++statistics.thread_exits; { // Loop until the handler id and priority can be communicated to // the main_program. int freed = 0; do { cyg_mutex_lock(&free_handler_lock); { if (-1 == free_handler_id) { free_handler_id = data; free_handler_pri = N_MAIN+(int) data; freed = 1; } } cyg_mutex_unlock(&free_handler_lock); if (!freed) cyg_thread_delay(2); } while (!freed); } // Then exit. cyg_thread_exit(); } /* look for an available handler thread */ int get_handler_slot(cyg_handle_t current_threadH) { int i; int found = 0; while (!found) { cyg_mutex_lock(&handler_slot_lock); { for (i = 0; i < MAX_HANDLERS; ++i) { if (!handler_thread_in_use[i]) { found = 1; handler_thread_in_use[i]++; break; } } } cyg_mutex_unlock(&handler_slot_lock); if (!found) cyg_thread_delay(1); } CYG_ASSERT(1 == handler_thread_in_use[i], "Handler usage count wrong!"); return i; } /* do things which will stress the system */ void perform_stressful_tasks() { #define MAX_MALLOCED_SPACES 100 /* do this many mallocs at most */ #define MALLOCED_BASE_SIZE 1 /* basic size in bytes */ char *spaces[MAX_MALLOCED_SPACES]; unsigned int i; cyg_mutex_t tmp_lock; cyg_uint8 pool_space[10][100]; cyg_handle_t mempool_handles[10]; cyg_mempool_fix mempool_objects[10]; cyg_mutex_init(&tmp_lock); /* here I use malloc, which uses the kernel's variable memory pools. note that malloc/free is a bit simple-minded here: it does not try to really fragment things, and it does not try to make the allocation/deallocation concurrent with other thread execution (although I'm about to throw in a yield()) */ for (i = 0; i < MAX_MALLOCED_SPACES; ++i) { ++statistics.malloc_tries; /* spaces[i] = (char *) malloc(((int)(sqrt(i*2.0))+1)*MALLOCED_BASE_SIZE); */ spaces[i] = (char *) malloc(((int)i*2.0+1)*MALLOCED_BASE_SIZE); if (i % (MAX_MALLOCED_SPACES/10) == 0) { cyg_thread_yield(); } if (i % (MAX_MALLOCED_SPACES/15) == 0) { cyg_thread_delay(i % 5); } } /* now free it all up */ for (i = 0; i < MAX_MALLOCED_SPACES; ++i) { if (spaces[i] != NULL) { unsigned int j; for (j = 0; j < (i*2+1)*MALLOCED_BASE_SIZE; ++j) { spaces[i][j] = 0xAA; /* write a bit pattern */ } free(spaces[i]); } else { ++statistics.malloc_failures; } } /* now allocate and then free some fixed-size memory pools; for now this is simple-minded because it does not have many threads sharing the memory pools and racing for memory. */ for (i = 0; i < 10; ++i) { cyg_mempool_fix_create(pool_space[i], 100, (i+1)*3, &mempool_handles[i], &mempool_objects[i]); } for (i = 0; i < 10; ++i) { spaces[i] = cyg_mempool_fix_try_alloc(mempool_handles[i]); } for (i = 0; i < 10; ++i) { if (spaces[i]) { cyg_mempool_fix_delete(mempool_handles[i]); } } cyg_mutex_destroy(&tmp_lock); } /* report_alarm_func() is invoked as an alarm handler, so it should be quick and simple. in this case it sets a global flag which is checked by main_program. */ void report_alarm_func(cyg_handle_t alarmH, cyg_addrword_t data) { time_to_report = 1; } /* this sets up death alarms. it gets the handle and alarm from the caller, since they must persist for the life of the alarm */ void setup_death_alarm(cyg_addrword_t data, cyg_handle_t *deathHp, cyg_alarm *death_alarm_p, int *killed_p) { cyg_handle_t system_clockH, counterH; cyg_resolution_t rtc_res; system_clockH = cyg_real_time_clock(); cyg_clock_to_counter(system_clockH, &counterH); cyg_alarm_create(counterH, death_alarm_func, (cyg_addrword_t) killed_p, deathHp, death_alarm_p); rtc_res = cyg_clock_get_resolution(system_clockH); { cyg_tick_count_t tick_delay; tick_delay = (long long) ((1000000000.0*rtc_res.divisor) *((double)DEATH_TIME_LIMIT)/((double)rtc_res.dividend)); if ( cyg_test_is_simulator ) tick_delay /= 10; #ifdef CYGPKG_HAL_I386_LINUX // 20 seconds is a long time compared to the run time of other tests. // Reduce to 10 seconds, allowing more tests to get run. tick_delay /= 2; #endif cyg_alarm_initialize(*deathHp, cyg_current_time() + tick_delay, 0); } } /* death_alarm_func() is the alarm handler that kills the current thread after a specified timeout. It does so by setting a flag the thread is constantly checking. */ void death_alarm_func(cyg_handle_t alarmH, cyg_addrword_t data) { int *killed_p; killed_p = (int *) data; *killed_p = 1; } /* now I write the sc_ versions of the cyg_functions */ void sc_thread_create( cyg_addrword_t sched_info, /* scheduling info (eg pri) */ cyg_thread_entry_t *entry, /* entry point function */ cyg_addrword_t entry_data, /* entry data */ char *name, /* optional thread name */ void *stack_base, /* stack base, NULL = alloc */ cyg_ucount32 stack_size, /* stack size, 0 = default */ cyg_handle_t *handle, /* returned thread handle */ cyg_thread *thread /* put thread here */ ) { /*printf("Creating a thread -- priority is %lu\n", (unsigned long) sched_info);*/ /* fflush(stdout); */ ++statistics.thread_creations; cyg_thread_create(sched_info, entry, entry_data, name, stack_base, stack_size, handle, thread); } void print_statistics(void) { int i; cyg_mutex_lock(&statistics_print_lock); { printf("Handler-invocations: "); for (i = 0; i < MAX_HANDLERS; ++i) { printf("%4lu ", statistics.handler_invocation_histogram[i]); } printf("\n"); printf("malloc()-tries/failures: -- %7lu %7lu\n", statistics.malloc_tries, statistics.malloc_failures); printf("client_makes_request: %d\n", client_makes_request); } cyg_mutex_unlock(&statistics_print_lock); } #else /* (CYGNUM_KERNEL_SCHED_PRIORITIES >= */ /* (N_MAIN+N_CLIENTS+N_LISTENERS+MAX_HANDLERS)) */ #define N_A_MSG "not enough priorities available" #endif /* (CYGNUM_KERNEL_SCHED_PRIORITIES >= */ /* (N_MAIN+N_CLIENTS+N_LISTENERS+MAX_HANDLERS)) */ #else /* CYGSEM_LIBC_MALLOC */ # define N_A_MSG "this test needs malloc" #endif /* CYGSEM_LIBC_MALLOC */ #else /* CYGFUN_KERNEL_THREADS_TIMER */ # define N_A_MSG "this test needs kernel threads timer" #endif /* CYGFUN_KERNEL_THREADS_TIMER */ #else /* CYGPKG_LIBM */ # define N_A_MSG "this test needs libm" #endif /* CYGPKG_LIBM */ #else /* CYGSEM_LIBC_STDIO */ # define N_A_MSG "this test needs stdio" #endif /* CYGSEM_LIBC_STDIO */ #else // def CYGFUN_KERNEL_API_C # define N_A_MSG "this test needs Kernel C API" #endif #else // def CYGPKG_KERNEL && CYGPKG_IO && CYGPKG_LIBC # define N_A_MSG "this tests needs Kernel, libc and IO" #endif #ifdef N_A_MSG externC void cyg_start( void ) { CYG_TEST_INIT(); CYG_TEST_NA( N_A_MSG); } #endif // N_A_MSG
