daemon and user application programs. The ideas are based on
suggestions from Jeff Mogul and Philip Gladstone and a similar
interface designed by the latter. It is important to point out that
the functionality of the original Unix adjtime() system call is
preserved, so that the modified kernel will work as the unmodified
one, should the new features not be in use. In this case the
ntp_adjtime() system call can still be used to read and write kernel
variables that might be used by a synchronization daemon other than
NTP, for example.
4.1. The ntp_gettime() System Call
The syntax and semantics of the ntp_gettime() call are given in
the following fragment of the timex.h header file. This file is
identical, except for the SHIFT_HZ define, in the SunOS, Ultrix
and OSF/1 kernel distributions. (The SHIFT_HZ define represents
the logarithm to the base 2 of the clock oscillator frequency
specific to each system type.) Note that the timex.h file calls
the syscall.h system header file, which must be modified to define
the SYS_ntp_gettime system call specific to each system type. The
kernel distributions include directions on how to do this.
/*
* This header file defines the Network Time Protocol (NTP)
* interfaces for user and daemon application programs. These are
* implemented using private system calls and data structures and
* require specific kernel support.
*
* NAME
* ntp_gettime - NTP user application interface
*
* SYNOPSIS
* #include <sys/timex.h>
*
* int system call(SYS_ntp_gettime, tptr)
*
* int SYS_ntp_gettime defined in syscall.h header file
* struct ntptimeval *tptr pointer to ntptimeval structure
*
* NTP user interface - used to read kernel clock values
* Note: maximum error = NTP synch distance = dispersion + delay /
* 2
* estimated error = NTP dispersion.
*/
struct ntptimeval {
struct timeval time; /* current time */
long maxerror; /* maximum error (us) */
long esterror; /* estimated error (us) */
};
The ntp_gettime() system call returns three values in the
ntptimeval structure: the current time in unix timeval format plus
the maximum and estimated errors in microseconds. While the 32-bit
long data type limits the error quantities to something more than
an hour, in practice this is not significant, since the protocol
itself will declare an unsynchronized condition well below that
limit. In the NTP Version 3 specification, if the protocol
computes either of these values in excess of 16 seconds, they are
clamped to that value and the system clock declared
unsynchronized.
Following is a detailed description of the ntptimeval structure
members.
struct timeval time; /* current time */
This member returns the current system time, expressed as a
Unix timeval structure. The timeval structure consists of two
32-bit words; the first returns the number of seconds past 1
January 1970, while the second returns the number of
microseconds.
long maxerror; /* maximum error (us) */
This member returns the time_maxerror kernel variable in
microseconds. See the entry for this variable in section 5 for
additional information.
long esterror; /* estimated error (us) */
This member returns the time_esterror kernel variable in
microseconds. See the entry for this variable in section 5 for
additional information.
4.2. The ntp_adjtime() System Call
The syntax and semantics of the ntp_adjtime() call are given in
the following fragment of the timex.h header file. Note that, as
in the ntp_gettime() system call, the syscall.h system header file
must be modified to define the SYS_ntp_adjtime system call
specific to each system type.
/*
* NAME
* ntp_adjtime - NTP daemon application interface
*
* SYNOPSIS
* #include <sys/timex.h>
*
* int system call(SYS_ntp_adjtime, mode, tptr)
*
* int SYS_ntp_adjtime defined in syscall.h header file
* struct timex *tptr pointer to timex structure
*
* NTP daemon interface - used to discipline kernel clock
* oscillator
*/
struct timex {
int mode; /* mode selector */
long offset; /* time offset (us) */
long frequency; /* frequency offset (scaled ppm) */
long maxerror; /* maximum error (us) */
long esterror; /* estimated error (us) */
int status; /* clock command/status */
long time_constant; /* pll time constant */
long precision; /* clock precision (us) (read only)
*/
long tolerance; /* clock frequency tolerance (scaled
* ppm) (read only) */
/*
* The following read-only structure members are implemented
* only if the PPS signal discipline is configured in the
* kernel.
*/
long ybar; /* frequency estimate (scaled ppm) */
long disp; /* dispersion estimate (scaled ppm)
*/
int shift; /* interval duration (s) (shift) */
long calcnt; /* calibration intervals */
long jitcnt; /* jitter limit exceeded */
long discnt; /* dispersion limit exceeded */
};
The ntp_adjtime() system call is used to read and write certain
time-related kernel variables summarized in this and subsequent
sections. Writing these variables can only be done in superuser
mode. To write a variable, the mode structure member is set with
one or more bits, one of which is assigned each of the following
variables in turn. The current values for all variables are
returned in any case; therefore, a mode argument of zero means to
return these values without changing anything.
Following is a description of the timex structure members.
int mode; /* mode selector */
This is a bit-coded variable selecting one or more structure
members, with one bit assigned each member. If a bit is set,
the value of the associated member variable is copied to the
corresponding kernel variable; if not, the member is ignored.
The bits are assigned as given in the following fragment of the
timex.h header file. Note that the precision and tolerance are
determined by the kernel and cannot be changed by
ntp_adjtime().
/*
* Mode codes (timex.mode)
*/
#define ADJ_OFFSET 0x0001 /* time offset */
#define ADJ_FREQUENCY 0x0002 /* frequency offset */
#define ADJ_MAXERROR 0x0004 /* maximum time error */
#define ADJ_ESTERROR 0x0008 /* estimated time error */
#define ADJ_STATUS 0x0010 /* clock status */
#define ADJ_TIMECONST 0x0020 /* pll time constant */
long offset; /* time offset (us) */
If selected, this member replaces the value of the time_offset
kernel variable in microseconds. The absolute value must be
less than MAXPHASE microseconds defined in the timex.h header
file. See the entry for this variable in section 5 for
additional information.
If within range and the PPS signal and/or external oscillator
are configured and operating properly, the clock status is
automatically set to TIME_OK.
long time_constant; /* pll time constant */
If selected, this member replaces the value of the
time_constant kernel variable. The value must be between zero
and MAXTC defined in the timex.h header file. See the entry for
this variable in section 5 for additional information.
long frequency; /* frequency offset (scaled ppm) */
If selected, this member replaces the value of the
time_frequency kernel variable. The value is in ppm, with the
integer part in the high order 16 bits and fraction in the low
order 16 bits. The absolute value must be in the range less
than MAXFREQ ppm defined in the timex.h header file. See the
entry for this variable in section 5 for additional
information.
long maxerror; /* maximum error (us) */
If selected, this member replaces the value of the
time_maxerror kernel variable in microseconds. See the entry
for this variable in section 5 for additional information.
long esterror; /* estimated error (us) */
If selected, this member replaces the value of the
time_esterror kernel variable in microseconds. See the entry
for this variable in section 5 for additional information.
int status; /* clock command/status */
If selected, this member replaces the value of the time_status
kernel variable. See the entry for this variable in section 5
for additional information.
In order to set this variable by ntp_adjtime(), either (a) the
current clock status must be TIME_OK or (b) the member value is
TIME_BAD; that is, the ntp_adjtime() call can always set the
clock to the unsynchronized state or, if the clock is running
correctly, can set it to any state. In any case, the
ntp_adjtime() call always returns the current state in this
member, so the caller can determine whether or not the request
succeeded.
long time_constant; /* pll time constant */
If selected, this member replaces the value of the
time_constant kernel variable. The value must be between zero
and MAXTC defined in the timex.h header file. See the entry for
this variable in section 5 for additional information.
long precision; /* clock precision (us) (read only) */
This member returns the time_precision kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
long tolerance; /* clock frequency tolerance (scaled ppm)
*/
This member returns the time_tolerance kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
long ybar; /* frequency estimate (scaled ppm) */
This member returns the pps_ybar kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
long disp; /* dispersion estimate (scaled ppm) */
This member returns the pps_disp kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
int shift; /* interval duration (s) (shift) */
This member returns the pps_shift kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
long calcnt; /* calibration intervals */
This member returns the pps_calcnt kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
long jitcnt; /* jitter limit exceeded */
This member returns the pps_jittcnt kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
long discnt; /* dispersion limit exceeded */
This member returns the pps_discnt kernel variable in
microseconds. The variable can be written only by the kernel.
See the entry for this variable in section 5 for additional
information.
4.3. Command/Status Codes
The kernel routines use 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 as the result code by both the ntp_gettime() and
ntp_adjtime() system calls. In addition, it can be explicitly read
and written using the ntp_adjtime() system call, but can be
written only in superuser mode. Values presently defined in the
timex.h header file are as follows:
/*
* Clock command/status codes (timex.status)
*/
#define TIME_OK 0 /* clock synchronized */
#define TIME_INS 1 /* insert leap second */
#define TIME_DEL 2 /* delete leap second */
#define TIME_OOP 3 /* leap second in progress */
#define TIME_BAD 4 /* kernel clock not synchronized */
#define TIME_ERR 5 /* external oscillator not
synchronized */
A detailed description of these codes as used by the leap-second
state machine is given later in this memorandum. In case of a
negative result code, the kernel has intercepted an invalid
address or (in case of the ntp_adjtime() system call), a superuser
violation.
5. Kernel Variables
This section contains a list of kernel variables and a detailed
description of their function, initial value, scaling and limits.
5.1. Interface Variables
The following variables are read and set by the ntp_adjtime()
system call. Additional automatic variables are used as
temporaries as described in the code fragments.
int time_status = TIME_BAD;
This variable controls the state machine used to insert or
delete leap seconds and show the status of the timekeeping
system, PPS signal and external oscillator, if configured.
long time_offset = 0;
This variable is used by the PLL to adjust the system time in
small increments. It is scaled by (1 << SHIFT_UPDATE) (12) in
microseconds. The maximum value that can be represented is
about +-512 ms and the minimum value or precision is a few
parts in 10^10 s.
long time_constant = 0; /* pll time constant */
This variable determines the bandwidth or "stiffness" of the
PLL. The value is used as a shift between zero and MAXTC (6),
with the effective PLL time constant equal to a multiple of (1
<< time_constant) in seconds. For room-temperature quartz
oscillator the recommended default value is 2, which
corresponds to a PLL time constant of about 900 s and a maximum
update interval of about 64 s. The maximum update interval
scales directly with the time constant, so that at the maximum
time constant of 6, the update interval can be as large as 1024
s.
Values of time_constant between zero and 2 can be used if quick
convergence is necessary; values between 2 and 6 can be used to
reduce network load, but at a modest cost in accuracy. Values
above 6 are appropriate only if an external oscillator is
present.
long time_tolerance = MAXFREQ; /* frequency tolerance (ppm) */
This variable represents the maximum frequency error or
tolerance in ppm of the particular CPU clock oscillator and is
a property of the architecture; however, in principle it could
change as result of the presence of external discipline
signals, for instance. It is expressed as a positive number
greater than zero in parts-per-million (ppm).
The recommended value of MAXFREQ is 200 ppm is appropriate for
room-temperature quartz oscillators used in typical
workstations. However, it can change due to the operating
condition of the PPS signal and/or external oscillator. With
either the PPS signal or external oscillator, the recommended
value for MAXFREQ is 100 ppm.
long time_precision = 1000000 / HZ; /* clock precision (us) */
This variable represents the maximum error in reading the
system clock in microseconds. It is usually based on the number
of microseconds between timer interrupts, 10000 us for the
SunOS kernel, 3906 us for the Ultrix kernel, 976 us for the
OSF/1 kernel. However, in cases where the time can be
interpolated between timer interrupts with microsecond
resolution, such as in the unmodified SunOS kernel and modified
Ultrix and OSF/1 kernels, the precision is specified as 1 us.
In cases where a PPS signal or external oscillator is
available, the precision can depend on the operating condition
of the signal or oscillator. This variable is determined by the
kernel for use by the synchronization daemon, but is otherwise
not used by the kernel.
long time_maxerror = MAXPHASE; /* maximum error */
This variable establishes the maximum error of the indicated
time relative to the primary synchronization source in
microseconds. For NTP, the value is initialized by a
ntp_adjtime() call to the synchronization distance, which is
equal to the root dispersion plus one-half the root delay. It
is increased by a small amount (time_tolerance) each second to
reflect the clock frequency tolerance. This variable is
computed by the synchronization daemon and the kernel, but is
otherwise not used by the kernel.
long time_esterror = MAXPHASE; /* estimated error */
This variable establishes the expected error of the indicated
time relative to the primary synchronization source in
microseconds. For NTP, the value is determined as the root
dispersion, which represents the best estimate of the actual
error 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 synchronization daemon and
returned in system calls, but is otherwise not used by the
kernel.
5.2. Phase-Lock Loop Variables
The following variables establish the state of the PLL and the
residual time and frequency offset of the system clock. Additional
automatic variables are used as temporaries as described in the
code fragments.
long time_phase = 0; /* phase offset (scaled us) */
The time_phase variable represents the phase of the kernel time
variable at each tick of the clock. This variable is scaled by
(1 << SHIFT_SCALE) (23) in microseconds, giving a maximum
adjustment of about +-256 us/tick and a resolution less than
one part in 10^12.
long time_offset = 0; /* time offset (scaled us) */
The time_offset variable represents the time offset of the CPU
clock oscillator. It is recalculated as each update to the
system clock is received via the hardupdate() routine and at
each second in the seconds_overflow routine. This variable is
scaled by (1 << SHIFT_UPDATE) (12) in microseconds, giving a
maximum adjustment of about +-512 ms and a resolution of a few
parts in 10^10 s.
long time_freq = 0; /* frequency offset (scaled ppm) */
The time_freq variable represents the frequency offset of the
CPU clock oscillator. It is recalculated as each update to the
system clock is received via the hardupdate() routine. It can
also be set via ntp_adjtime() from a value stored in a file
when the synchronization daemon is first started. It can be
retrieved via ntp_adjtime() and written to the file about once
per hour by the daemon. The time_freq variable is scaled by (1
<< SHIFT_KF) (16) ppm, giving it a maximum value well in excess
of the limit of +-256 ppm imposed by other constraints. The
precision of this representation (frequency resolution) is
parts in 10^11, which is adequate for all but the best external
oscillators.
time_adj = 0; /* tick adjust (scaled 1 / HZ) */
The time_adj variable is the adjustment added to the value of
tick at each timer interrupt. It is computed once each second
from the time_offset, time_freq and, if the PPS signal is
present, the ps_ybar variable once each second.
long time_reftime = 0; /* time at last adjustment (s) */
This variable is the seconds portion of the system time on the
last update received by the hardupdate() routine. It is used to
compute the time_freq variable as the time since the last
update increases.
int fixtick = 1000000 % HZ; /* amortization factor */
In the Ultrix and OSF/1 kernels, the interval between timer
interrupts does not evenly divide the number of microseconds in
the second. In order that the clock runs at a precise rate, it
is necessary to introduce an amortization factor into the local
timescale. In the original Unix code, the value of fixtick is
amortized once each second, introducing an additional source of
jitter; in the new model the value is amortized at each tick of
the system clock, reducing the jitter by the reciprocal of the
clock oscillator frequency. This is not a new kernel variable,
but a new use of an existing kernel variable.
5.3. Pulse-per-second (PPS) Frequency-Lock Loop Variables
The following variables are used only if a pulse-per-second (PPS)
signal is available and connected via a modem-control lead, such
as produced by the optional ppsclock feature incorporated in the
serial port driver. They establish the design parameters of the
PPS frequency-lock loop used to discipline the CPU clock
oscillator to an external PPS signal. Additional automatic
variables are used as temporaries as described in the code
fragments.
long pps_usec; /* microseconds at last pps */
The pps_usec variable is latched from a high resolution counter
or external oscillator at each PPS interrupt. In determining
this value, only the hardware counter contents are used, not
the contents plus the kernel time variable, as returned by the
microtime() routine.
long pps_ybar = 0; /* pps frequency offset estimate */
The pps_ybar variable is the average CPU clock oscillator
frequency offset relative to the PPS disciplining signal. It is
scaled in the same units as the time_freq variable.
pps_disp = MAXFREQ; /* dispersion estimate (scaled ppm) */
The pps_disp variable represents the average sample dispersion
measured over the last three samples. It is scaled in the same
units as the time_freq variable.
pps_dispmax = MAXFREQ / 2; /* dispersion threshold */
The pps_dispmax variable is used as a dispersion threshold. If
pps_disp is less than this threshold, the median sample is used
to update the pps_ybar estimate; if not, the sample is
discarded.
pps_dispinc = MAXFREQ >> (PPS_SHIFT + 4); /* pps dispersion
increment/sec */
The pps_dispinc variable is the increment to add to pps_disp
once each second. It is computed such that, if no PPS samples
have arrived for several calibration intervals, the value of
pps_disp will exceed the pps_dispmax threshold and raise an
alarm.
int pps_mf[] = {0, 0, 0}; /* pps median filter */
The pps-mf[] array is used as a median filter to detect and
discard jitter in the PPS signal.
int pps_count = 0; /* pps calibrate interval counter */
The pps_count variable measures the length of the calibration
interval used to calculate the frequency. It normally counts
from zero to the value 1 << pps_shift.
pps_shift = PPS_SHIFT; /* interval duration (s) (shift) */
The pps_shift variable determines the duration of the
calibration interval, 1 << pps_shift s.
pps_intcnt = 0; /* intervals at current duration */
The pps_intcnt variable counts the number of calibration
intervals at the current interval duration. It is reset to zero
after four intervals and when the interval duration is changed.
long pps_calcnt = 0; /* calibration intervals */
The pps_calcnt variable counts the number of calibration
intervals.
long pps_jitcnt = 0; /* jitter limit exceeded */
The pps_jitcnt variable counts the number of resets due to
excessive jitter or frequency offset. These resets are
usually due to excessive noise in the PPS signal or
interface.
long pps_discnt = 0; /* dispersion limit exceeded */
The pps_discnt variable counts the number of calibration
intervals where the dispersion is above the pps_dispmax
limit. These resets are usually due to excessive frequency
wander in the PPS signal source.
5.4. External Oscillator Variables
The following variables are used only if an external oscillator
(HIGHBALL or TPRO) is present. Additional automatic variables are
used as temporaries as described in the code fragments.
int clock_count = 0; /* CPU clock counter */
The clock_count variable counts the seconds between adjustments
to the kernel time variable to discipline it to the external
clock.
struct timeval clock_offset; /* HIGHBALL clock offset */
The clock_offset variable defines the offset between system
time and the HIGHBALL counters.
long clock_cpu = 0; /* CPU clock adjust */
The clock_cpu variable contains the offset between the system
clock and the HIGHBALL clock for use in disciplining the kernel
time variable.
6. Architecture Constants
Following is a list of the important architecture constants that
establish the response and stability of the PLL and provide maximum
bounds on behavior in order to satisfy correctness assertions made in
the protocol specification. Additional definitions are given in the
timex.h header file.
6.1. Phase-lock loop (PLL) definitions
The following defines establish the performance envelope of the
PLL. They establish the maximum phase error (MAXPHASE), maximum
frequency error (MAXFREQ), minimum interval between updates
(MINSEC) and maximum interval between updates (MAXSEC). The intent
of these bounds is to force the PLL to operate within predefined
limits in order to satisfy correctness assertions of the
synchronization protocol. An excursion which exceeds these bounds
is clamped to the bound and operation proceeds normally. 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.
MAXPHASE must be set greater than or equal to CLOCK.MAX (128 ms),
as defined in the NTP specification. CLOCK.MAX establishes the
maximum time offset allowed before the system time is reset,
rather than incrementally adjusted. Here, the maximum offset is
clamped to MAXPHASE only in order to prevent overflow errors due
to defective programming.
MAXFREQ reflects the manufacturing frequency tolerance of the CPU
oscillator plus the maximum slew rate allowed by the protocol. It
should be set to at least the intrinsic frequency tolerance of the
oscillator plus 100 ppm for vernier frequency adjustments. If the
kernel frequency discipline code is installed (PPS_SYNC), the CPU
oscillator frequency is disciplined to an external source,
presumably with negligible frequency error.
#define MAXPHASE 512000 /* max phase error (us) */
#ifdef PPS_SYNC
#define MAXFREQ 100 /* max frequency error (ppm) */
#else
#define MAXFREQ 200 /* max frequency error (ppm) */
#endif /* PPS_SYNC */
#define MINSEC 16 /* min interval between updates (s)
*/
#define MAXSEC 1200 /* max interval between updates (s)
*/
6.2. Pulse-per-second (PPS) Frequency-lock Loop (FLL) Definitions
The following defines and declarations are used only if a pulse-
per-second (PPS) signal is available and connected via a modem-
control lead, such as produced by the optional ppsclock feature
incorporated in the serial port driver. They establish the design
parameters of the frequency-lock loop (FLL) used to discipline the
CPU clock oscillator to the PPS oscillator.
PPS_AVG is the averaging constant used to update the FLL from
frequency samples measured for each calibration interval.
PPS_SHIFT and PPS_SHIFTMAX are the minimum and maximem,
respectively, of the calibration interval represented as a power
of two. The PPS_DISPINC is the initial increment to pps_disp at
each second.
#define PPS_AVG 2 /* pps averaging constant (shift) */
#define PPS_SHIFT 2 /* min interval duration (s) (shift)
*/
#define PPS_SHIFTMAX 6 /* max interval duration (s) (shift)
*/
#define PPS_DISPINC 0 /* dispersion increment (us/s) */
6.3. External Oscillator Definitions
The following definitions and declarations are used only if an
external oscillator (HIGHBALL or TPRO) is configured on the
system.
#define CLOCK_INTERVAL 30 /* CPU clock update interval (s) */
7. References
[1] Mills, D., "Internet time synchronization: the Network Time
Protocol", IEEE Trans. Communications COM-39, 10 (October 1991),
1482- 1493. Also in: Yang, Z., and T.A. Marsland (Eds.). Global
States and Time in Distributed Systems, IEEE Press, Los Alamitos,
CA, 91-102.
[2] Mills, D., "Network Time Protocol (Version 3) specification,
implementation and analysis", RFC1305, University of Delaware,
March 1992, 113 pp.
[3] Mills, D., "Modelling and analysis of computer network clocks",
Electrical Engineering Department Report 92-5-2, University of
Delaware, May 1992, 29 pp.
[4] Mills, D., "Simple Network Time Protocol (SNTP)", RFC1361,
University of Delaware, August 1992, 10 pp.
[5] Mills, D., "Precision synchronizatin of computer network clocks",
Electrical Engineering Department Report 93-11-1, University of
Delaware, November 1993, 66 pp.
Security Considerations
Security issues are not discussed in this memo.
Author's Address
David L. Mills
Electrical Engineering Department
University of Delaware
Newark, DE 19716
Phone: (302) 831-8247
EMail: mills@udel.edu