597 lines
30 KiB
Plaintext
597 lines
30 KiB
Plaintext
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
|