On Sun, 7 Mar 1999, Stephane Pierre Bordas wrote:
> I am confronted to persons that think that RTL is not reliable because it
> is a freeware and thus would prefer to use Windows for applications
> needing calculations of robot arm trajectories requiring a data treatment
> as fast as one millisecond (at least 10 milliseconds).
>
> I don't think this would be a good idea to use Windows with such small
> durations, but I am facing the reluctancy of my partners and would like to
> know your advice on that.
Unlike many of the very smart people on this list who have always used
RT-Linux, or another RTOS, I started my project as your peers suggest,
with a Redmond OS known as NT.
This was a bad idea.
>From this experience I can say NT won't work. But, hopefully I can do a
little better and offer some code tidbits so that you can write some NT
code to show that it can not work.
First you need access to NT performance counters - the same can be
done in RT-Linux using the rdtsc() call of the kernel as suggested by
Mantegazza to examine jitter (Note: you will need to replace the DEBUG()
macro with printf() or whatever - this is what's in GError.h):
GPerfCounter.h
/*
/* Quick & Dirty Performance Counter Routines for Windows NT
/*
/* QP_START - starting point for timing
/* QP_END - ending point for timing
/* QP_SECONDS - returns a double giving duration in seconds
/*
/* Macros set QPCStartMark() and QPCEndMark() in the code and
/* QPCDurationInSeconds returns the time between the end
/* and start marks in seconds.
*/
// GPerfCounter.h
#ifndef G_PERFCOUNTER_HEADER
#define G_PERFCOUNTER_HEADER
#ifdef NT_PERFORMANCE_TIMING
BOOL QPCStartMark();
BOOL QPCEndMark();
double QPCDurationInSeconds();
#define QP_START QPCStartMark()
#define QP_STOP QPCEndMark()
#define QP_SECONDS QPCDurationInSeconds()
#else
#define QP_START
#define QP_STOP
#define QP_SECONDS -1.0
#endif
/* End Query Performace Counter Routines
*/
#endif
// GPerfCounter.h
GPerCounter.cpp:
/*
/* Quick & Dirty Performance Counter Routines for Windows NT
/*
/* QP_START - starting point for timing
/* QP_END - ending point for timing
/* QP_SECONDS - returns a double giving duration in seconds
/*
/* Macros set QPCStartMark() and QPCEndMark() in the code and
/* QPCDurationInSeconds returns the time between the end
/* and start marks in seconds.
*/
#include "GError.h"
static _int64 liQPCounter1 = 0;
static _int64 liQPCounter2 = 0;
BOOL QPCStartMark()
{
return QueryPerformanceCounter( (LARGE_INTEGER *)&liQPCounter1 );
}
BOOL QPCEndMark()
{
return QueryPerformanceCounter( (LARGE_INTEGER *)&liQPCounter2 );
}
double QPCDurationInSeconds()
{
/*Variables for the NT Performance Counter system*/
static double dSecondsPerCount = 0.0;
_int64 liQPFrequency = 0;
_int64 liQPCountDifference = 0;
if ( 0.0 == dSecondsPerCount ) { /*frequency not yet set*/
/*Get the frequency of the performance counter
(counts/sec)*/
if ( 0 == QueryPerformanceFrequency( \
(LARGE_INTEGER*)&liQPFrequency) ) {
/*No performance counter is available*/
return -1.0;
}
DEBUG(10, ("QueryPerformanceFrequency returns %I64d counts/second\n",
liQPFrequency));
dSecondsPerCount = ( 1.0/(double )liQPFrequency );
}
liQPCountDifference = liQPCounter2 - liQPCounter1;
return liQPCountDifference * dSecondsPerCount;
}
/* End Query Performace Counter Routines
*/
If you imagine your RT task running in a service, as I did, so that it is
not subject to the tampering of the user, then you will need some other
routines as well. In any case, you will have a high priority loop
servicing the task, perhaps something like:
//Setup the Thread...
//Make this service the most important thing in user-space;
//this will not prevent ISRs, kernel level DPCs and whatnot from
//blowing this thread away. GUI routines may invert the priority
//scheme and make a mess (minimize/maximize a window).
//NT is _not_ a Real Time Operating System.
if ( !SetPriority( REALTIME_PRIORITY_CLASS,
THREAD_PRIORITY_TIME_CRITICAL) )
return DAQSVC_ERR_PRIORITY_NOT_SET;
/* the main service loop will run until the RunServiceMutex is
released */
do {
//do what you need to ...
//This is a REALTIME priority thread so give some CPU time
//to other threads; this loop will run up CPU time to 100%
//under the NT Task Manager (shift-ctrl-esc).
YieldProcessorTime(0);
}
/* Repeat while RunServiceMutex is still taken. */
while ( (WAIT_TIMEOUT == dwRunMutex) && (bExecuteLoop) );
This provides for a "polling mode" type of task, of course, NT has a rich
object set, so you can wait on a variety of other object types instead.
For example, you could wait for input from some client task via a shared
mutex, or whatever. Such a wait will not make a difference as far as the
task timing is concerned, ISR/DPCs in the NT kernel will still insert
lags in your regularly scheduled task (try minimizing then maximizing
windows, it's amazing how long this will delay a top priority task;
recall that under NT 4.0 the display is integrated into the kernel).
These are some routines to help you with priorities and sleeping:
///////////////////////////////////////////////////////////////////////////////
//
// BOOL SetPriority( DWORD dwPriorityClass, int nPriority )
//
// Purpose:
// Sets the priority and priority class of the current process and
// thread.
//
// Parameters:
// dwPriorityClass -The process class
// nPriority -The priority within the given
// process class
//
// Returns:
// TRUE on success, FALSE otherwise.
//
BOOL SetPriority( DWORD dwPriorityClass, int nPriority )
{
DWORD dwReturnedPriorityClass = 0;
int nReturnedPriority = 0;
//Run this process with the specified priority class
if (FALSE == SetPriorityClass(GetCurrentProcess(), \
dwPriorityClass)) {
//Could not set the priority class
return FALSE;
}
//Double check that the was actually given; it is possible
//that SetPriorityClass might simply set the highest priority it
//can, but fail to provide the requested (e.g. REALTIME) priority.
dwReturnedPriorityClass = GetPriorityClass(GetCurrentProcess());
if (dwReturnedPriorityClass != dwPriorityClass) {
//Could not set the specified priority class
return FALSE;
}
//Set the thread priority within the specified process class
return SetThreadPriority(GetCurrentThread(), nPriority);
}
///////////////////////////////////////////////////////////////////////////////
//
// void YieldProcessorTime( DWORD dwMilliseconds )
//
// Purpose:
// Surrender time slice to other ready threads. Calls SwitchToThread
// if nSeconds equals 0 or Sleep( nTime ).
//
// Parameters:
// nTime -Time to sleep in milliseconds.
//
// Returns:
// void.
//
// Comments:
// Sleep is not guaranteed to return after nTime milliseconds, the
// time to return may be longer.
//
void YieldProcessorTime( DWORD dwMilliseconds )
{
QP_START;
if ( 0 < dwMilliseconds ) Sleep(dwMilliseconds);
//May sleep longer!
else if ( 0 == dwMilliseconds ) SwitchToThread();
//On the current CPU only
QP_STOP;
DEBUG(10, ("YieldProcessorTime: %f\n", QP_SECONDS));
}
NT is really a pretty nice general purpose OS, it just isn't suited to
hard real-time work. When you look at all the advantages of Linux, and
throw RT into the mix, technically, you really don't have a choice.
Good luck,
-Don
--- [rtl] ---
To unsubscribe:
echo "unsubscribe rtl" | mail [EMAIL PROTECTED] OR
echo "unsubscribe rtl <Your_email>" | mail [EMAIL PROTECTED]
----
For more information on Real-Time Linux see:
http://www.rtlinux.org/~rtlinux/