// $Id$ // ============================================================================ // // = LIBRARY // performance-tests/Misc // // = FILENAME // preempt.cpp // // = DESCRIPTION // This is a simple test to illustrate OS thread preemption. One // ore more high priority threads periodically (every half // second, by default) reads the clock. They use select () to // block for that duration. Meanwhile, a low priority thread // continually chews up the CPU. Without preemption, the high // priority thread won't have a chance to read the clock in a // timely manner. // // At the end of the test, the actual clock read intervals by the // high priority task(s) are printed out. With proper // preemption, the intervals should correspond to the requested // clock read interval. // // There is a -y option for the low priority thread to periodically // yield. It shouldn't be necessary to use that option, if preemption // is supported. It's a handy option for testing. // // = AUTHOR // David L. Levine // // ============================================================================ #include "ace/Task.h" #include "ace/Sched_Params.h" #include "ace/Get_Opt.h" ACE_RCSID(Misc, preempt, "$Id$") #if defined (ACE_HAS_THREADS) || ! defined (ACE_LACKS_FORK) #if defined (ACE_HAS_STHREADS) # include /* for _lwp_self () */ #endif /* ACE_HAS_STHREADS */ static const char usage [] = "[-? |\n" #if defined (ACE_HAS_THREADS) " [-f use fork instead of spawn]\n" #endif /* ACE_HAS_THREADS */ " [-h ]\n" " [-l ]\n" " [-n ]\n" " [-p ]\n" " [-y to yield the low priority thread]]\n"; // Configuration options. #if defined (ACE_HAS_THREADS) u_int use_fork = 0; #else /* ! ACE_HAS_THREADS */ u_int use_fork = 1; #endif /* ! ACE_HAS_THREADS */ u_int high_iterations = 10; u_int high_priority_tasks = 1; u_int low_iterations = 25000; /* Big enough to keep the low priority task cranking for a while */ u_long read_period = 500000; /* usec */ u_int low_yield = 0; // To permit calculation of relative times. ACE_hrtime_t starttime; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // class High_Priority_Task /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// class High_Priority_Task : public ACE_Task { public: High_Priority_Task (void); ~High_Priority_Task (void); int open (void *); int svc (void); int done () const { return done_; } void print_times () const; private: int priority_; int done_; u_long *time_; }; High_Priority_Task::High_Priority_Task (void) : ACE_Task (ACE_Thread_Manager::instance ()), priority_ (ACE_Sched_Params::next_priority ( ACE_SCHED_FIFO, ACE_Sched_Params::priority_min (ACE_SCHED_FIFO, ACE_SCOPE_THREAD), ACE_SCOPE_THREAD)), done_ (0) { ACE_NEW (time_, u_long[high_iterations]); } High_Priority_Task::~High_Priority_Task (void) { delete [] time_; time_ = 0; } int High_Priority_Task::open (void *) { if (use_fork == 1) { ACE_hthread_t thr_handle; ACE_Thread::self (thr_handle); if (ACE_Thread::setprio (thr_handle, priority_) == -1) ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "setprio failed"), -1); return svc (); } else { long flags = THR_BOUND | THR_NEW_LWP | THR_SCHED_FIFO; // Become an active object. if (this->activate (flags, 1, 0, this->priority_) == -1) { ACE_DEBUG ((LM_ERROR, "(%P|%t) task activation failed, exiting!\n")); ACE_OS::exit (1); } return 0; } } int High_Priority_Task::svc (void) { ACE_hthread_t thr_handle; ACE_Thread::self (thr_handle); int prio; if (ACE_Thread::getprio (thr_handle, prio) == -1) ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "getprio failed"), -1); #if defined (ACE_HAS_STHREADS) ACE_DEBUG ((LM_DEBUG, "(%P|%u|%t) High: prio = %d, priority_ = %d\n", _lwp_self (), prio, this->priority_)); #else /* ! ACE_HAS_STHREADS */ ACE_DEBUG ((LM_DEBUG, "(%P|%t) High: prio = %d, priority_ = %d\n", prio, this->priority_)); #endif /* ! ACE_HAS_STHREADS */ ACE_ASSERT (this->priority_ == prio); ACE_Time_Value pause (0, read_period); for (u_int i = 0; i < high_iterations; ++i) { time_ [i] = (u_long) ((ACE_OS::gethrtime () - starttime)/ (ACE_UINT32) 1000000u); ACE_OS::select (0, 0, 0, 0, &pause); } done_ = 1; return 0; } void High_Priority_Task::print_times () const { for (u_int i = 0; i < high_iterations; ++i) { ACE_DEBUG ((LM_INFO, " iteration %u, time %u msec\n", i, time_ [i])); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // class Low_Priority_Task /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// class Low_Priority_Task : public ACE_Task { public: Low_Priority_Task (const High_Priority_Task &); int open (void *); int svc (void); private: int priority_; const High_Priority_Task &high_priority_task_; }; Low_Priority_Task::Low_Priority_Task ( const High_Priority_Task &high_priority_task) : ACE_Task (ACE_Thread_Manager::instance ()), priority_ (ACE_Sched_Params::priority_min (ACE_SCHED_FIFO, ACE_SCOPE_THREAD)), high_priority_task_ (high_priority_task) { } int Low_Priority_Task::open (void *) { if (use_fork == 1) { ACE_hthread_t thr_handle; ACE_Thread::self (thr_handle); if (ACE_Thread::setprio (thr_handle, priority_) == -1) ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "setprio failed"), -1); return svc (); } else { long flags = THR_BOUND | THR_NEW_LWP | THR_SCHED_FIFO; // Become an active object. if (this->activate (flags, 1, 0, this->priority_) == -1) ACE_ERROR ((LM_ERROR, "(%P|%t) task activation failed, exiting!\n%a", -1)); return 0; } } int Low_Priority_Task::svc (void) { ACE_hthread_t thr_handle; ACE_Thread::self (thr_handle); int prio; if (ACE_Thread::getprio (thr_handle, prio) == -1) ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "getprio failed"), -1); #if defined (ACE_HAS_STHREADS) ACE_DEBUG ((LM_DEBUG, "(%P|%u|%t) Low: prio = %d, priority_ = %d\n", _lwp_self (), prio, this->priority_)); #else /* ! ACE_HAS_STHREADS */ ACE_DEBUG ((LM_DEBUG, "(%P|%t) Low: prio = %d, priority_ = %d\n", prio, this->priority_)); #endif /* ! ACE_HAS_STHREADS */ ACE_ASSERT (this->priority_ == prio); u_long iterations = 0; // Chew up CPU for the entire interval. for (; ! high_priority_task_.done () && iterations < low_iterations; ++iterations) { u_long n = 1279ul; /* Takes about 40.2 usecs on a 168 MHz Ultra2. */ ACE::is_prime (n, 2ul /* min_factor */, n/2 /* max_factor */); if (low_yield) ACE_OS::thr_yield (); } ACE_DEBUG ((LM_INFO, "Low completed %u iterations\n", iterations)); return 0; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // function get_options /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// static int get_options (int argc, ACE_TCHAR *argv[]) { ACE_Get_Opt get_opt (argc, argv, ACE_TEXT("fh:l:n:p:y?")); int opt; while ((opt = get_opt ()) != EOF) { switch (opt) { case 'f': use_fork = 1; break; case 'h': if (ACE_OS::atoi (get_opt.opt_arg ()) >= 2) high_iterations = ACE_OS::atoi (get_opt.opt_arg ()); else ACE_ERROR_RETURN ((LM_ERROR, "%n: high iterations must be >= 2\n"), -1); break; case 'l': if (ACE_OS::atoi (get_opt.opt_arg ()) >= 2) low_iterations = ACE_OS::atoi (get_opt.opt_arg ()); else ACE_ERROR_RETURN ((LM_ERROR, "%n: low iterations must be >= 2\n"), -1); break; case 'n': if (ACE_OS::atoi (get_opt.opt_arg ()) >= 1) high_priority_tasks = ACE_OS::atoi (get_opt.opt_arg ()); else ACE_ERROR_RETURN ((LM_ERROR, "%n: number of high priority threads " "must be >= 1\n"), -1); break; case 'p': if (ACE_OS::atoi (get_opt.opt_arg ()) > 0) read_period = ACE_OS::atoi (get_opt.opt_arg ()); else ACE_ERROR_RETURN ((LM_ERROR, "%n: read period > 0\n"), -1); break; case 'y': low_yield = 1; break; case '?': ACE_DEBUG ((LM_ERROR, "usage: %n %s\n%a", usage, 0)); break; default: ACE_ERROR_RETURN ((LM_ERROR, "%n: unknown arg, %c\nusage: %n %s\n", opt, usage), -1); } } switch (argc - get_opt.opt_ind ()) { case 0: // OK, no non-flag arguments. break; default: ACE_ERROR_RETURN ((LM_ERROR, "%n: too many arguments\nusage: %n %s\n", usage), -1); } return 0; } #endif /* ACE_HAS_THREADS || ! ACE_LACKS_FORK */ /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // function main /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// int ACE_TMAIN (int argc, ACE_TCHAR *argv[]) { ACE_LOG_MSG->open (argv[0] ? argv[0] : ACE_TEXT("preempt")); #if defined (ACE_HAS_THREADS) || !defined (ACE_LACKS_FORK) u_int i; if (get_options (argc, argv)) ACE_OS::exit (-1); // Enable FIFO scheduling, e.g., RT scheduling class on Solaris. if (ACE_OS::sched_params ( ACE_Sched_Params ( ACE_SCHED_FIFO, ACE_Sched_Params::priority_min (ACE_SCHED_FIFO), ACE_SCOPE_PROCESS)) != 0) { if (ACE_OS::last_error () == EPERM) ACE_DEBUG ((LM_MAX, "preempt: user is not superuser, " "so remain in time-sharing class\n")); else ACE_ERROR_RETURN ((LM_ERROR, "%n: ACE_OS::sched_params failed\n%a"), -1); } High_Priority_Task *high_priority_task; ACE_NEW_RETURN (high_priority_task, High_Priority_Task [high_priority_tasks], 1); Low_Priority_Task low_priority_task (high_priority_task[0]); if (! use_fork) { ACE_DEBUG ((LM_DEBUG, "(%P|%t) main (), wait for threads to exit . . .\n")); } // Save the start time, so that deltas can be displayed later. starttime = ACE_OS::gethrtime (); // Spawn the threads/processes . . . pid_t child = 0; if (use_fork == 1) { switch ((child = ACE_OS::fork (ACE_TEXT("preempt-low_priority_process")))) { case -1: ACE_ERROR ((LM_ERROR, "%p\n%a", "fork failed")); return -1; case 0: // In child { low_priority_task.open (0); break; } default: // In parent for (i = 0; i < high_priority_tasks; ++i) { high_priority_task[i].open (0); } break; } } else { for (i = 0; i < high_priority_tasks; ++i) { high_priority_task[i].open (0); } low_priority_task.open (0); #if defined (ACE_HAS_THREADS) // Wait for all threads to exit. ACE_Thread_Manager::instance ()->wait (); #endif /* ACE_HAS_THREADS */ } // Display the time deltas. They should be about a half second apart. if (child || ! use_fork) { for (i = 0; i < high_priority_tasks; ++i) { ACE_DEBUG ((LM_DEBUG, "High priority task %u:\n", i + 1)); high_priority_task[i].print_times (); } } delete [] high_priority_task; #else /* ! ACE_HAS_THREADS && ACE_LACKS_FORK */ ACE_UNUSED_ARG (argc); ACE_UNUSED_ARG (argv); ACE_ERROR ((LM_ERROR, "threads and fork not supported on this platform\n")); #endif /* ! ACE_HAS_THREADS && ACE_LACKS_FORK */ return 0; }