diff options
Diffstat (limited to 'usr.sbin/xntpd/kernel/README.kern')
-rw-r--r-- | usr.sbin/xntpd/kernel/README.kern | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/usr.sbin/xntpd/kernel/README.kern b/usr.sbin/xntpd/kernel/README.kern new file mode 100644 index 0000000..64ba9c5 --- /dev/null +++ b/usr.sbin/xntpd/kernel/README.kern @@ -0,0 +1,596 @@ +Precision Time and Frequency Synchronization Using Modified Kernels + +1. Introduction + +This memo describes replacements for certain SunOS and Ultrix kernel +routines that manage the system clock and timer functions. They provide +improved accuracy and stability through the use of a disciplined clock +interface for use with the Network Time Protocol (NTP) or similar time- +synchronization protocol. In addition, for certain models of the +DECstation 5000 product line, the new routines provide improved +precision to +-1 microsecond (us) (SunOS 4.1.1 already does provide +precision to +-1 us). The current public NTP distribution cooperates +with these kernel routines to provide synchronization in principle to +within a microsecond, but in practice this is limited by the short-term +stability of the oscillator that drives the timer interrupt. + +This memo describes the principles behind the design and operation of +the software. There are two versions of the software, one that operates +with the SunOS 4.1.1 kernel and the other that operates with the Ultrix +4.2a kernel (and probably the 4.3 kernel, although this has not been +tested). A detailed description of the variables and algorithms is given +in the hope that similar improvements can be incorporated in Unix +kernels for other machines. The software itself is not included in this +memo, since it involves licensed code. Detailed instructions on where to +obtain it for either SunOS or Ultrix will be given separately. + +The principle function added to the SunOS and Ultrix kernels is to +change the way the system clock is controlled, in order to provide +precision time and frequency adjustments. Another function utilizes an +undocumented counter in the DECstation hardware to provide precise time +to the microsecond. This function can be used only with the DECstation +5000/240 and possibly others that use the same input/output chipset. + +2. Design Principles + +In order to understand how these routines work, it is useful to consider +how most Unix systems maintain the system clock. In the original design +a hardware timer interrupts the kernel at some fixed rate, such as 100 +Hz in the SunOS kernel and 256 Hz in the Ultrix kernel. Since 256 does +not evenly divide the second in microseconds, the kernel inserts 64 us +once each second so that the system clock stays in step with real time. +The time returned by the gettimeofday() routine is thus characterized by +255 advances of 3906 us plus one of 3970 us. + +Also in the original design it is possible to slew the system clock to a +new offset using the adjtime() system call. To do this the clock +frequency is changed by adding or subtracting a fixed amount (tickadj) +at each timer interrupt (tick) for a calculated number of ticks. Since +this calculation involves dividing the requested offset by tickadj, it +is possible to slew to a new offset with a precision only of tickadj, +which is usually in the neighborhood of 5 us, but sometimes much higher. + +In order to maintain the system clock within specified bounds with this +scheme, it is necessary to call adjtime() on a regular basis. For +instance, let the bound be set at 100 us, which is a reasonable value +for NTP-synchronized hosts on a local network, and let the onboard +oscillator tolerance be 100 ppm, which is a reasonably conservative +assumption. This requires that adjtime() be called at intervals not +exceeding 1 second (s), which is in fact what the unmodified NTP +software daemon does. + +In the modified kernel routines this scheme is replaced by another that +extends the low-order bits of the system clock to provide very precise +clock adjustments. At each timer interrupt a precisely calibrated time +adjustment is added to the composite time value and overflows handled as +required. The quantity to add is computed from the adjtime() call and, +in addition a frequency adjustment, which is automatically calculated +from previous time adjustments. This implementation operates as an +adaptive-parameter, first-order, type-II, phase-lock loop (PLL), which +in principle provides precision control of the system clock phase to +within +-1 us and frequency to within +-5 nanoseconds (ns) per day. + +This PLL model is identical to the one implemented in NTP, except that +in NTP the software daemon has to simulate the PLL using only the +original adjtime() system call. The daemon is considerably complicated +by the need to parcel time adjustments at frequent intervals in order to +maintain the accuracy to specified bounds. The kernel routines do this +directly, allowing vast gobs of ugly daemon code to be avoided at the +expense of only a small amount of new code in the kernel. In fact, the +amount of code added to the kernel for the new scheme is about the +amount removed for the old scheme. The new adjtime() routine needs to be +called only as each new time update is determined, which in NTP occurs +at intervals of from 64 s to 1024 s. In addition, doing the frequency +correction in the kernel means that the system time runs true even if +the daemon were to cease operation or the network paths to the primary +reference source fail. + +Note that the degree to which the adjtime() adjustment can be made is +limited to a specific maximum value, presently +-128 milliseconds (ms), +in order to achieve microsecond resolution. It is the intent in the +design that settimeofday() be used for changes in system time greater +than +-128 ms. It has been the Internet experience that the need to +change the system time in increments greater than +-128 milliseconds is +extremely rare and is usually associated with a hardware or software +malfunction. Nevertheless, the limit applies to each adjtime() call and +it is possible, but not recommended, that this routine is called at +intervals smaller than 64 seconds, which is the NTP lower limit. + +For the most accurate and stable operation, adjtime() should be called +at specified intervals; however, the PLL is quite forgiving and neither +moderate loss of updates nor variations in the length of the interval is +serious. The current engineering parameters have been optimized for +intervals not greater than about 64 s. For larger intervals the PLL time +constant can be adjusted to optimize the dynamic response up to +intervals of 1024 s. Normally, this is automatically done by NTP. In any +case, if updates are suspended, the PLL coasts at the frequency last +determinated, which usually results in errors increasing only to a few +tens of milliseconds over a day. + +The new code needs to know the initial frequency offset and time +constant for the PLL, and the daemon needs to know the current frequency +offset computed by the kernel for monitoring purposes. This is provided +by a small change in the second argument of the kernel adjtime() calling +sequence, which is documented later in this memo. Ordinarily, only the +daemon will call the adjtime() routine, so the modified calling sequence +is easily accommodated. Other than this change, the operation of +adjtime() is transparent to the original. + +In the DECstation 5000/240 and possibly other models there happens to be +an undocumented hardware register that counts system bus cycles at a +rate of 25 MHz. The new kernel routines test for the CPU type and, in +the case of the '240, use this register to interpolate system time +between hardware timer interrupts. This results in a precision of +-1 us +for all time values obtained via the gettimeofday() system call. This +routine calls the kernel routine microtime(), which returns the actual +interpolated value, but does not change the kernel time variable. +Therefore, other kernel routines that access the kernel time variable +directly and do not call either gettimeofday() or microtime() will +continue their present behavior. + +The new kernel routines include provisions for error statistics (maximum +error and estimated error), leap seconds and system clock status. These +are intended to support applications that need such things; however, +there are no applications other than the time-synchronization daemon +itself that presently use them. At issue is the manner in which these +data can be provided to application clients, such as new system calls +and data interfaces. While a proposed interface is described later in +this memo, it has not yet been implemented. This is an area for further +study. + +While any time-synchronization daemon can in principle be modified to +use the new code, the most likely will be users of the xntp3 +distribution of NTP. The code in the xntp3 distribution determines +whether the new kernel code is in use and automatically reconfigures as +required. When the new code is in use, the daemon reads the frequency +offset from a file and provides it and the initial time constant via +adjtime(). In subsequent calls to adjtime(), only the time adjustment +and time constant are affected. The daemon reads the frequency from the +kernel (returned as the second argument of adjtime()) at intervals of +one hour and writes it to the file. + +3. Technical Description + +Following is a technical description of how the new scheme works in +terms of the variables and algorithms involved. These components are +discussed as a distinct entity and do not involve coding details +specific to the Ultrix kernel. The algorithms involve only minor changes +to the system clock and interval timer routines, but do not in +themselves provide a conduit for application programs to learn the +system clock status or statistics of the time-synchronization process. +In a later section a number of new system calls are proposed to do this, +along with an interface specification. + +The new scheme works like the companion simulator called kern.c and +included in this directory. This stand-alone simulator includes code +fragments identical to those in the modified kernel routines and +operates in the same way. The system clock is implemented in the kernel +using a set of variables and algorithms defined below and in the +simulator. The algorithms are driven by explicit calls from the +synchronization protocol as each time update is computed. The clock is +read and set using the gettimeofday() and settimeofday() system calls, +which operate in the same way as the originals, but return a status word +describing the state of the system clock. + +Once the system clock has been set, the adjtime() system call is used to +provide periodic updates including the time offset and possibly +frequency offset and time constant. With NTP this occurs at intervals of +from 64 s to 1024 s, deending on the time constant value. The kernel +implements an adaptive-parameter, first-order, type-II, phase-lock loop +(PLL) in order to integrate this offset into the phase and frequency of +the system clock. The kernel keeps track of the time of the last update +and adjusts the maximum error to grow by an amount equal to the +oscillator frequency tolerance times the elapsed time since the last +update. + +Occasionally, it is necessary to adjust the PLL parameters in response +to environmental conditions, such as leap-second warning and oscillator +stability observations. While the interface to do this has not yet been +implemented, proposals to to that are included in a later section. A +system call (setloop()) is used on such occasions to communicate these +data. In addition, a system call (getloop())) is used to extract these +data from the kernel for monitoring purposes. + +All programs utilize the system clock status variable time_status, which +records whether the clock is synchronized, waiting for a leap second, +etc. The value of this variable is returned by each system call. It can +be set explicitly by the setloop() system call and implicitly by the +settimeofday() system call and in the timer-interrupt routine. Values +presently defined in the header file timex.h are as follows: + +int time_status = TIME_BAD; /* clock synchronization status */ + +#define TIME_UNS 0 /* unspecified or unknown */ +#define TIME_OK 1 /* operation succeeded */ +#define TIME_INS 1 /* insert leap second at end of current day */ +#define TIME_DEL 2 /* delete leap second at end of current day */ +#define TIME_OOP 3 /* leap second in progress */ +#define TIME_BAD 4 /* system clock is not synchronized */ +#define TIME_ADR -1 /* operation failed: invalid address */ +#define TIME_VAL -2 /* operation failed: invalid argument */ +#define TIME_PRV -3 /* operation failed: priviledged operation */ + +In case of a negative result code, the operation has failed; however, +some variables may have been modified before the error was detected. +Note that the new system calls never return a value of zero, so it is +possible to determine whether the old routines or the new ones are in +use. The syntax of the modified adjtime() is as follows: + +/* + * adjtime - adjuts system time + */ +#include <sys/timex.h> + +int gettimexofday(tp, fiddle) + +struct timeval *tp; /* system time adjustment*/ +struct timeval *fiddle; /* sneak path */ + +On entry the "timeval" sneak path is coded: + +struct timeval { + long tv_sec = time_constant; /* time constant */ + long tv_usec = time_freq; /* new frequency offset */ +} + +However, the sneak is ignored if fiddle is the null pointer and the new +frequency offset is ignored if zero. + +The value returned on exit is the system clock status defined above. The +"timeval" sneak path is modified as follows: + +struct timeval { + long tv_sec = time_precision; /* system clock precision */ + long tv_usec = time_freq; /* current frequency offset */ +} + +3.1. Kernel Variables + +The following variables are used by the new code: + +long time_offset = 0; /* time adjustment (us) */ + +This variable is used by the PLL to adjust the system time in small +increments. It is scaled by (1 << SHIFT_UPDATE) in binary microseconds. +The maximum value that can be represented is about +-130 ms and the +minimum value or precision is about one nanosecond. + +long time_constant = SHIFT_TAU; /* pll time constant */ + +This variable determines the bandwidth or "stiffness" of the PLL. It is +used as a shift, with the effective value in positive powers of two. The +optimum value for this variable is equal to 1/64 times the update +interval. The default value SHIFT_TAU (0) corresponds to a PLL time +constant of about one hour or an update interval of about one minute, +which is appropriate for typical uncompensated quartz oscillators used +in most computing equipment. Values larger than four are not useful, +unless the local clock timebase is derived from a precision oscillator. + +long time_tolerance = MAXFREQ; /* frequency tolerance (ppm) */ + +This variable represents the maximum frequency error or tolerance of the +particular platform and is a property of the architecture. It is +expressed as a positive number greater than zero in parts-per-million +(ppm). The default MAXFREQ (100) is appropriate for conventional +workstations. + +long time_precision = 1000000 / HZ; /* clock precision (us) */ + +This variable represents the maximum error in reading the system clock. +It is expressed as a positive number greater than zero in microseconds +and is usually based on the number of microseconds between timer +interrupts, in the case of the Ultrix kernel, 3906. However, in cases +where the time can be interpolated between timer interrupts with +microsecond resolution, the precision is specified as 1. This variable +is computed by the kernel for use by the time-synchronization daemon, +but is otherwise not used by the kernel. + +struct timeval time_maxerror; /* maximum error */ + +This variable represents the maximum error, expressed as a Unix timeval, +of the system clock. For NTP, it is computed as the synchronization +distance, which is equal to one-half the root delay plus the root +dispersion. It is increased by a small amount (time_tolerance) each +second to reflect the clock frequency tolerance. This variable is +computed by the time-synchronization daemon and the kernel for use by +the application program, but is otherwise not used by the kernel. + +struct timeval time_esterror; /* estimated error */ + +This variable represents the best estimate of the actual error, +expressed as a Unix timeval, of the system clock based on its past +behavior, together with observations of multiple clocks within the peer +group. This variable is computed by the time-synchronization daemon for +use by the application program, but is otherwise not used by the kernel. + +The PLL itself is controlled by the following variables: + +long time_phase = 0; /* phase offset (scaled us) */ +long time_freq = 0; /* frequency offset (scaled ppm) */long +time_adj = 0; /* tick adjust (scaled 1 / HZ) */ + +These variables control the phase increment and the frequency increment +of the system clock at each tick of the clock. The time_phase variable +is scaled by (1 << SHIFT_SCALE) in binary microseconds, giving a minimum +value (time resolution) of 9.3e-10 us. The time_freq variable is scaled +by (1 << SHIFT_KF) in parts-per-million (ppm), giving it a maximum value +of about +-130 ppm and a minimum value (frequency resolution) of 6e-8 +ppm. The time_adj variable is the actual phase increment in scaled +microseconds to add to time_phase once each tick. It is computed from +time_phase and time_freq once per second. + +long time_reftime = 0; /* time at last adjustment (s) */ + +This variable is the second's portion of the system time on the last +call to adjtime(). It is used to adjust the time_freq variable as the +time since the last update increases. + +The HZ define establishes the timer interrupt frequency, 256 Hz for the +Ultrix kernel and 100 Hz for the SunOS kernel. The SHIFT_HZ define +expresses the same value as the nearest power of two in order to avoid +hardware multiply operations. These are the only parameters that need to +be changed for different timer interrupt rates. + +#define HZ 256 /* timer interrupt frequency (Hz) */ +#define SHIFT_HZ 8 /* log2(HZ) */ + +The following defines establish the engineering parameters of the PLL +model. They are chosen for an initial convergence time of about an hour, +an overshoot of about seven percent and a final convergence time of +several hours, depending on initial frequency error. + +#define SHIFT_KG 10 /* shift for phase increment */ +#define SHIFT_KF 24 /* shift for frequency increment */ +#define SHIFT_TAU 0 /* default time constant (shift) */ + +The SHIFT_SCALE define establishes the decimal point on the time_phase +variable which serves as a an extension to the low-order bits of the +system clock variable. The SHIFT_UPDATE define establishes the decimal +point of the phase portion of the adjtime() update. The FINEUSEC define +represents 1 us in scaled units. + +#define SHIFT_SCALE 28 /* shift for scale factor */ +#define SHIFT_UPDATE 14 /* shift for offset scale factor */ +#define FINEUSEC (1 << SHIFT_SCALE) /* 1 us in scaled units */ + +The FINETUNE define represents the residual, in ppm, to be added to the +system clock variable in addition to the integral 1-us value given by +tick. This allows a systematic frequency offset in cases where the timer +interrupt frequency does not exactly divide the second in microseconds. + +#define FINETUNE (1000000 - (1000000 / HZ) * HZ) /* frequency adjustment + * for non-isochronous HZ (ppm) */ + +The following four defines establish the performance envelope of the +PLL, one to bound the maximum phase error, another to bound the maximum +frequency error and the last two to bound the minimum and maximum time +between updates. The intent of these bounds is to force the PLL to +operate within predefined limits in order to conform to the correctness +models assumed by time-synchronization protocols like NTP and DTSS. An +excursion which exceeds these bounds is clamped to the bound and +operation proceeds accordingly. In practice, this can occur only if +something has failed or is operating out of tolerance, but otherwise the +PLL continues to operate in a stable mode. Note that the MAXPHASE define +conforms to the maximum offset allowed in NTP before the system time is +reset, rather than incrementally adjusted. + +#define MAXPHASE 128000 /* max phase error (us) */ +#define MINSEC 64 /* min interval between updates (s) */ +#define MAXFREQ 100 /* max frequency error (ppm) */ +#define MAXSEC 1024 /* max interval between updates (s) */ + +3.2. Code Segments + +The code segments illustrated in the simulator should make clear the +operations at various points in the code. These segments are not derived +from any licensed code. The hardupdate() fragment is called by adjtime() +to update the system clock phase and frequency. This is an +implementation of an adaptive-parameter, first-order, type-II phase-lock +loop. Note that the time constant is in units of powers of two, so that +multiplies can be done by simple shifts. The phase variable is computed +as the offset multiplied by the time constant. Then, the time since the +last update is computed and clamped to a maximum (for robustness) and to +zero if initializing. The offset is multiplied (sorry about the ugly +multiply) by the result and by the square of the time constant and then +added to the frequency variable. Finally, the frequency variable is +clamped not to exceed the tolerance. Note that all shifts are assumed to +be positive and that a shift of a signed quantity to the right requires +a litle dance. + +With the defines given, the maximum time offset is determined by the +size in bits of the long type (32) less the SHIFT_UPDATE (14) scale +factor or 18 bits (signed). The scale factor is chosen so that there is +no loss of significance in later steps, which may involve a right shift +up to 14 bits. This results in a maximum offset of about +-130 ms. Since +the time_constant must be greater than or equal to zero, the maximum +frequency offset is determined by the SHIFT_KF (24) scale factor, or +about +-130 ppm. In the addition step the value of offset * mtemp is +represented in 18 + 10 = 28 bits, which will not overflow a long add. +There could be a loss of precision due to the right shift of up to eight +bits, since time_constant is bounded at four. This results in a net +worst-case frequency error of about 2^-16 us or well down into the +oscillator phase noise. While the time_offset value is assumed checked +before entry, the time_phase variable is an accumulator, so is clamped +to the tolerance on every call. This helps to damp transients before the +oscillator frequency has been determined, as well as to satisfy the +correctness assertions if the time-synchronization protocol comes +unstuck. + +The hardclock() fragment is inserted in the hardware timer interrupt +routine at the point the system clock is to be incremented. The phase +adjustment (time_adj) is added to the clock phase (time_phase) and +tested for overflow of the microsecond. If an overflow occurs, the +microsecond (tick) in incremented or decremented. + +The second_overflow() fragment is inserted at the point where the +microseconds field of the system time variable is being checked for +overflow. On rollover of the second the maximum error is increased by +the tolerance. The time offset is divided by the phase weight (SHIFT_KG) +and time constant. The time offset is then reduced by the result and the +result is scaled and becomes the value of the phase adjustment. The +phase adjustment is then corrected for the calculated frequency offset +and a fixed offset FINETUNE which is a property of the architecture. On +rollover of the day the leap-warning indicator is checked and the +apparent time adjusted +-1 s accordingly. The gettimeofday() routine +insures that the reported time is always monotonically increasing. + +The simulator can be used to check the loop operation over the design +range of +-128 ms in time error and +-100 ppm in frequency error. This +confirms that no overflows occur and that the loop initially converges +in about 50-60 minutes for timer interrupt rates from 50 Hz to 1024 Hz. +The loop has a normal overshoot of about seven percent and a final +convergence time of several hours, depending on the initional frequency +error. + +3.3. Leap Seconds + +The leap-warning condition is determined by the synchronization protocol +(if remotely synchronized), by the timecode receiver (if available), or +by the operator (if awake). The time_status value must be set on the day +the leap event is to occur (30 June or 31 December) and is automatically +reset after the event. If the value is TIME_DEL, the kernel adds one +second to the system time immediately following second 23:59:58 and +resets time_status to TIME_OK. If the value is TIME_INS, the kernel +subtracts one second from the system time immediately following second +23:59:59 and resets time_status to TIME_OOP, in effect causing system +time to repeat second 59. Immediately following the repeated second, the +kernel resets time_status to TIME_OK. + +Depending upon the system call implementation, the reported time during +a leap second may repeat (with a return code set to advertise that fact) +or be monotonically adjusted until system time "catches up" to reported +time. With the latter scheme the reported time will be correct before +and after the leap second, but freeze or slowly advance during the leap +second itself. However, Most programs will probably use the ctime() +library routine to convert from timeval (seconds, microseconds) format +to tm format (seconds, minutes,...). If this routine is modified to +inspect the return code of the gettimeofday() routine, it could simply +report the leap second as second 60. + +To determine local midnight without fuss, the kernel simply finds the +residue of the time.tv_sec value mod 86,400, but this requires a messy +divide. Probably a better way to do this is to initialize an auxiliary +counter in the settimeofday() routine using an ugly divide and increment +the counter at the same time the time.tv_sec is incremented in the timer +interrupt routine. For future embellishment. + +4. Proposed Application Program Interface + +Most programs read the system clock using the gettimeofday() system +call, which returns the system time and time-zone data. In the modified +5000/240 kernel, the gettimeofday() routine calls the microtime() +routine, which interpolates between hardware timer interrupts to a +precision of +-1 microsecond. However, the synchronization protocol +provides additional information that will be of interest in many +applications. For some applications it is necessary to know the maximum +error of the reported time due to all causes, including those due to the +system clock reading error, oscillator frequency error and accumulated +errors due to intervening time servers on the path to a primary +reference source. However, for those protocols that adjust the system +clock frequency as well as the time offset, the errors expected in +actual use will almost always be much less than the maximum error. +Therefore, it is useful to report the estimated error, as well as the +maximum error. + +It does not seem useful to provide additional details private to the +kernel and synchronization protocol, such as stratum, reference +identifier, reference timestamp and so forth. It would in principle be +possible for the application to independently evaluate the quality of +time and project into the future how long this time might be "valid." +However, to do that properly would duplicate the functionality of the +synchronization protocol and require knowledge of many mundane details +of the platform architecture, such as the tick value, reachability +status and related variables. Therefore, the application interface does +not reveal anything except the time, timezone and error data. + +With respect to NTP, the data maintained by the protocol include the +roundtrip delay and total dispersion to the source of synchronization. +In terms of the above, the maximum error is computed as half the delay +plus the dispersion, while the estimated error is equal to the +dispersion. These are reported in timeval structures. A new system call +is proposed that includes all the data in the gettimeofday() plus the +two new timeval structures. + +The proposed interface involves modifications to the gettimeofday(), +settimeofday() and adjtime() system calls, as well as new system calls +to get and set various system parameters. In order to minimize +confusion, by convention the new system calls are named with an "x" +following the "time"; e.g., adjtime() becomes adjtimex(). The operation +of the modified gettimexofday(), settimexofday() and adjtimex() system +calls is identical to that of their prototypes, except for the error +quantities and certain other side effects, as documented below. By +convention, a NULL pointer can be used in place of any argument, in +which case the argument is ignored. + +The synchronization protocol daemon needs to set and adjust the system +clock and certain other kernel variables. It needs to read these +variables for monitoring purposes as well. The present list of these +include a subset of the variables defined previously: + +long time_precision +long time_timeconstant +long time_tolerance +long time_freq +long time_status + +/* + * gettimexofday, settimexofday - get/set date and time + */ +#include <sys/timex.h> + +int gettimexofday(tp, tzp, tmaxp, testp) + +struct timeval *tp; /* system time */ +struct timezone *tzp; /* timezone */ +struct timeval *tmaxp; /* maximum error */ +struct timeval *testp; /* estimated error */ + +The settimeofday() syntax is identical. Note that a call to +settimexofday() automatically results in the system being declared +unsynchronized (TIME_BAD return code), since the synchronization +condition can only be achieved by the synchronization daemon using an +internal or external primary reference source and the adjtimex() system +call. + +/* + * adjtimex - adjust system time + */ +#include <sys/timex.h> + +int adjtimex(tp, tzp, freq, tc) + +struct timeval *tp; /* system time */ +struct timezone *tzp; /* timezone */ +long freq; /* frequency adjustment */ +long tc; /* time constant */ + +/* + * getloop, setloop - get/set kernel time variables + */ +#include <sys/timex.h> + +int getloop(code, argp) + +int code; /* operation code */ +long *argp; /* argument pointer */ + +The paticular kernal variables affected by these routines are selected +by the operation code. Values presently defined in the header file +timex.h are as follows: + +#define TIME_PREC 1 /* precision (log2(sec)) */ +#define TIME_TCON 2 /* time constant (log2(sec) */ +#define TIME_FREQ 3 /* frequency tolerance */ +#define TIME_FREQ 4 /* frequency offset (scaled) */ +#define TIME_STAT 5 /* status (see return codes) */ + +The getloop() syntax is identical. + +Comments welcome, but very little support is available: + +David L. Mills +Electrical Engineering Department +University of Delaware +Newark, DE 19716 +302 831 8247 fax 302 831 4316 +mills@udel.edu |