Novell is now a part of Micro Focus

Controlling the Server Clock Under NetWare 4

Articles and Tips: article

STEVE WINN
Senior Software Engineer
NetWare Products Division

01 Nov 1994


This Developer Note documents the NetWare 4 structure which controls the tick rate of the OS clock and provides access to information about time zones and daylight savings time. The TIMESYNC NLM uses the clock structure to achieve time synchronization among servers. The interface for getting and setting information in the structure is documented. The information provided in this DevNote could be used to augment or replace the TIMESYNC NLM.

Introduction

Beginning with NetWare 4.0, a mechanism for controlling the tick rate of the clock was incorporated into the NetWare OS. This incorporation was intended to simplify time synchronization. This DevNote explains the time synchronization interface. Some related information, such as time zone and daylight savings time status, is also mentioned.

Some of the information discussed here is closely tied to the operation of TIMESYNC.NLM, although this DevNote does not attempt to explain the inner workings of TIMESYNC.

The listings referred to in the text are found at the end of the DevNote.

The Synchronized Clock Structure

Listing 1 shows an internal OS structure commonly called the Synchronized Clock. It is not the only structure involved in keeping track of time in the OS but provides a convenient method of gaining information about the parameters affecting the time of day clock on the server and altering some of that information.

The fields of most interest are clock, tickIncrement, adjustmentCount, adjustmentValue, and grossCorrection. The clock field, as well as the tickIncrement, adjustmentValue and grossCorrection, consists of two parts: whole seconds and fractional seconds. Conceptually, there is a binary point between the whole and fractional seconds parts. The whole seconds part of the clock holds UTC time seconds since the start of 1970. The NetWare UTC time does not account for leap seconds.

Operation of the Clock

During initialization, NetWare reads the hardware clock on the host system and sets the UTC clock. NetWare also determines the value of tickIncrement at this time. The influence of time zone and daylight savings time information on time calculations is discussed in a separate section. The following code sample illustrates what NetWare does during each timer interrupt (a clock tick):

clock = clock + tickIncrement;     

     if (adjustmentCount > 1)     >
     {             

          clock = clock + adjustmentValue;          

          adjustmentCount = adjustmentCount - 1;     

     }     

     else if (adjustmentCount == 1)

     {          

          clock = clock + grossCorrection;          

          adjustmentCount = adjustmentCount - 1;     

     }

After updating the UTC clock, NetWare updates a second clock structure holding local time at the same rate. So, local time calculations are affected by the clock adjustments the same as UTC.

This particular algorithm was chosen with the task of time synchronization in mind. The adjustmentValue determines how much gradual correction will be applied to the clock during each timer interrupt and the grossCorrection holds a remainder. Thus, if a clock error of ERR seconds is to be corrected over a period of N clock interrupts the following calculations provide appropriate values (ignoring potential problems, such as division by zero).

adjustmentCount = N     

   adjustmentValue = ERR/(N 1) 
   grossCorrection = ERR mod (N 1) 

Note: The grossCorrection and adjustmentValue can be positive or negative. In actual practice, a further restriction should be enforced due to the fact that time has never been known to move backwards: when negative, the adjustmentValue and grossCorrection should be strictly smaller in magnitude than the tick increment. That is, the clock should never tick backwards!

A primary use of UTC time is as a timestamp for ordering events. As such, UTC should increase monotonically.

Important: Many applications use time stamps to sort events and rely heavily on the fact that time does not move backwards. In particular, Directory Services relies on time stamps derived from UTC time. Almost any application which uses a time stamp will be adversely effected if time appears to move backwards. For that reason, even setting the time backwards on an operating server to correct an obvious error can have drastic consequences and should be avoided whenever possible. It is important that time be set correctly when the server boots, or adjusted as quickly as possible thereafter, so that applications will not be adversely affected by backwards corrections.

This is a convenient place to explain a subtle difference between setting the time directly and applying one or more adjustments to the clock. Applying adjustments does not generate a time change event which is detectable through the event notification interface. The adjustment mechanism was intended to allow a way to correct small time errors gradually, so that they would not be noticed. It is possible to apply very large adjustments by the same means, but this is not recommended.

The Interface To The Synchronized Clock

Two functions are provided for accessing the synchronized clock structure:

  • GetSyncClockFields

  • SetSyncClockFields

These functions are not covered by the NLM library (CLIB) and may be imported directly. Currently, their use does not require CLIB context. However they should be considered blocking calls and should not be called at interrupt time in anticipation of future changes. Each function, as shown in Listing 1, expects a pointer to a clock structure and a bit mask which indicates which fields are of interest. Bits in the mask must be set in order to transfer data to or from the OS structure, so you don't have to supply all of the information if you only want to change one field.

A CLIB interface to the UTC clock and status fields of the synchronized clock structure is provided by the call GetClockStatus. For more information, see the NLM Library Reference manuals for details of this CLIB call.

Debugging hint: The OS structure is named SynchronizedClock and may be visible by that name in the debugger. (If not, you will undoubtedly be able to use the debugger to find it!) However, don't attempt to read or write this structure directly from your NLM or you may run up against memory protection problems. Always use the get and set functions as they will save you from truly frightening debugging problems in the future.

While it may appear that the interface to the time zone information, timezoneOffset savings time information, etc., is provided, it is not. While information about current settings is stored in the Synchronized Clock structure and used in some calculations, the actual interface for setting these parameters is inside the OS. Attempting to change anything but the fields controlling the clock and tick rate may result in unpredictable side effects.

In order to make use of the interface it is particularly important to understand how the status flags are interpreted and what the side effects of setting certain fields are. These issues will be discussed next.

Status Flags

Listing 1 shows constants for several flags which isolate bit fields within the statusFlags field. The use of these fields is explained here. Three status bit fields are intended to allow an application to determine whether time synchronization is active on a server and to determine the status of the synchronization effort. It is important that these bit fields always function this way. The synchronizing agent is responsible for managing the bits.

CLOCK_SYNCHRONIZATION_IS_ACTIVE indicates that the TIMESYNC NLM (or some equivalent) is active. When this bit is set it is reasonable to expect that the CLOCK_IS_NETWORK_SYNCHRONIZED bit may eventually be set. When synchronization is active the setting of CLOCK_IS_SYNCHRONIZED mirrors the setting of CLOCK_IS_NETWORK_SYNCHRONIZED. However, if synchronization is not active then CLOCK_IS_SYNCHRONIZED should always be set to 1.

CLOCK_IS_NETWORK_SYNCHRONIZED is used by the synchronizing agent to indicate that the UTC clock is accurate.

Note: The interaction of these bits may not be obvious, at first. If an application wants a reliable network time stamp, it should first determine that synchronization is active and then watch for the CLOCK_IS_NETWORK_SYNCHRONIZED flag to set before using the UTC clock field. If time synchronization is not active then network synchronization will never be achieved and an application watching only CLOCK_IS_NETWORK_SYNCHRONIZED will block. However, if an application wants to operate whenever time is synchronized as well as it going to be, it can just watch the CLOCK_IS_SYNCHRONIZED bit and will not block when time synchronization is not active.)

The OS clears CLOCK_IS_NETWORK_SYNCHRONIZED whenever time related information changes which could effect the accuracy of the UTC clock. The synchronizing agent should monitor this bit to know when the OS is signalling that corrective action may be necessary. The TIMESYNC NLM checks this bit once a second.

The CLOCK_STATUS_SERVER_TYPE field is used by the TIMESYNC NLM to report the server type: SINGLE (5), REFERENCE (4), PRIMARY(3), SECONDARY (2). Some applications may look at this field to determine the nature of the TIMESYNC NLM which is loaded. The value is not used internally in the OS but is returned by some functions which report the clock status to clients. The value should reflect the nature of the synchronizing agent - SINGLE and REFERENCE servers generally have an external time source and will not adjust their clocks to agree with PRIMARY and SECONDARY servers. PRIMARY and SECONDARY time sources will adjust their clocks to agree with other time sources.

Other bits within the status flags are undefined and reserved for use by Novell.

Clock Control Fields

The clock field may be set if necessary. For more information, see the explanation of the relationship between local time and UTC later in this DevNote. When the clock is set a time change event occurs and may be detected through the event notification interface.

The tickIncrement, adjustmentCount, adjustmentValue, and grossCorrection fields may be changed as needed to control the clock, as explained earlier. If necessary, you may change the tickIncrement.

The stdTickIncrement field contains the value which is used to initialize the tickIncrement. The stdTickIncrement should not be changed as it is provided as a reference value, in case tickIncrement must be restored.

The eventOffset and eventTime Fields

When UTC is greater than or equal to eventTime an internal OS routine is notified and adds eventOffset[0] (the whole second part) to the UTC clock. This is the mechanism by which TIMESYNC schedules a time adjustment. The eventOffset[1] is used as a status word by TIMESYNC. If TIMESYNC is not loaded then the eventTime and eventOffset fields may be used similarly.

Daylight Savings Time Information Fields

The OS interface for setting and controlling Daylight Savings Time (DST) information is implemented in SET parameters. The values stored in the Synchronized Clock Structure should not be changed except through the set parameter interface, otherwise inconsistent information may be displayed by the set parameters.

The daylight field indicates whether the value of tzname[1] is valid. It will be true (non-zero) if the second abbreviation was detected in the time zone string.

The daylightOffset field is the number of seconds by which local time is adjusted when daylight savings time is in effect. It is controlled by the SET DAYLIGHT SAVINGS TIME OFFSET parameter.

The daylightOnOff field when true (non-zero) indicates that daylight savings time is currently in effect. The state is determined by the OS after analyzing the information provided by the SET START/END OF DAYLIGHT SAVINGS TIME parameters.

The fields startDSTime and stopDSTime hold the times at which the next DST transitions are scheduled to occur. The times are determined after examining the information provided in the SET START/END OF DAYLIGHT SAVINGS TIME parameters.

Time Zone Information Fields

The interface for setting and controlling time zone information is implemented through SET parameters in the OS. The values stored in the synchronized clock structure should not be changed except through the set parameter interface, otherwise inconsistent information may be displayed by the set parameters.

The timezoneOffset field holds the value in seconds by which the local time zone is offset from UTC. The value is extracted from the time provided by the SET TIME ZONE console command.

The tzname field contains two offsets into the timezoneString field where the standard and DST abbreviations for the timezone are stored. Both abbreviations appear as null terminated strings. If the daylight field is zero then an abbreviation for use during DST was not provided and tzname[1] will point to an empty string.

Note that the values in tzname are offsets, not pointers! The abbreviations begin at timezoneString[tzname[0]] and timezoneString[tzname[1]].

Interactions Between Local Time, UTC and Other Variables

The synchronized clock structure holds UTC and information which determines how local time can be calculated from UTC. For efficiency, local time is kept as a separate clock structure which is incremented along with UTC during each timer interrupt. The relationship between local time and UTC is described by the following equation:

Local = UTC - timezoneOffset + daylightOffset * daylightOnOff

assuming that daylightOnOff is always zero or one. This equation contains five variables of which only UTC cannot be set from the console. In order to maintain the relationship between local time and UTC one or the other must be recalculated whenever any variable changes. The general rule is quite simple, if UTC is set then the local time is recalculated, but if any other variable is set then UTC is recalculated.

When calling SetSyncClockFields to change the UTC clock local time will automatically be recalculated. Since local time is updated at the same rate as UTC time any adjustments made to the clock also effect the calculation of local time. The calculation of local time is performed after any field updates are applied.

An interface already exists for setting the local time from the system console or under program control. That interface updates the UTC clock in the synchronized clock structure. However, since only the whole seconds can be specified when setting local time, the subsecond portion of the UTC clock is set to zero when local time is set.

The Effect of Setting the Hardware Clock Bit

In the field mask used by GetSyncClockFields and SetSyncClockFields there is a SYNCCLOCK_HARDWARE_CLOCK_BIT. However, there is no hardware clock field in the synchronized clock structure. The effect of this bit differs between the two calls. This is the mechanism which the TIMESYNC NLM uses to implement the SET TIMESYNC HARDWARE CLOCK parameter.

When the hardware clock bit is set the first action of GetSyncClockFields is to read the hardware clock and set both local and UTC time from it. However, the subsecond counter in the UTC clock is not set to zero.

In a complimentary fashion, when the hardware clock bit is set the last action of SetSyncClockFields is to update the hardware clock. This is actually accomplished by setting the local time using UTC as a base and applying the necessary time zone and daylight savings time adjustments.

Achieving Very High Accuracy Time Synchronization

Careful readers (or those now curious enough to review the last few paragraphs) will note that setting local time normally clears the subsecond counter in the UTC clock. When SetSyncClockFields must recalculate local time it saves and restores the subsecond counter rather than letting it be set to zero.

This is a pragmatic solution to a tricky problem which resulted when a request to clear the counter was implemented. If a timer interrupt occurs during the time that SetSyncClockFields is setting local time and the hardware clock then the tick is lost when the subsecond counter is restored. Even worse, if the lost tick caused the whole seconds to increment then the restored value will cause it to increment again during the next interrupt and a lost tick will actually result in a gained second!

To avoid this tragedy, check the subsecond counter before updating the hardware clock and only perform the update when the subsecond counter is small. Alternately, delay setting the UTC clock and hardware clock until the subsecond counter in the UTC clock can be set to a known value (zero, perhaps) so that the problem does not occur.

Interactions With TIMESYNC

While the synchronized clock interface was designed to support the needs of the TIMESYNC NLM, every effort was made to separate the OS and TIMESYNC as much as possible. The OS knows very little about the functionality in TIMESYNC.

Since the TIMESYNC NLM may be unloaded it would be possible to write a different synchronization NLM without too much difficulty. In that case it is important to respect the two places where the OS is aware of time synchronization:

  1. The OS clears the CLOCK_IS_NETWORK_SYNCHRONIZED bit in the status flags to indicate that critical time parameters may have changed and that UTC time may need to be corrected.

  2. The status flags, which include a server type field, are passed to clients, both in the server and on workstations. It would be a good idea to set the field to one of the known values, described earlier, which best reflects how time is being controlled on the server.

It might be useful to augment the TIMESYNC NLM by supplying an external time signal or detecting and correcting for an inaccurately ticking clock. These goals can be accomplished by setting the UTC clock or adjusting the tickIncrement field as needed and allowing TIMESYNC to continue functioning as normal. TimeSync doesn't change the tick Increment while attempting to correct the clock, specifically so an add on product could safely do so.

Since there is no convenient interface to TIMESYNC which allows you to detect when the next synchronization period will start, you have to rely on a little trust and do some monitoring. If you need to know when the next attempt to synchronize will occur look at the adjustmentCount value.

The value which TIMESYNC places in the adjustmentCount is calculated to complete the adjustments a few ticks before TIMESYNC begins the next synchronization attempt. Of course, if the value is zero you don't know whether synchronization is just about to begin or simply that no adjustment was necessary during the last attempt.

The most reliable way to cooperate with TIMESYNC is to set the adjustmentCount to zero and clear the network synchronized status flag at the time you change other fields. This will have the effect of canceling the previous adjustment while notifying TIMESYNC that it should immediately begin another synchronization attempt. Listing 2 shows a simple code sequence which does what was just described.

If an external time signal is being supplied through a separate NLM it is important to configure the HARDWARE CLOCK and TYPE options correctly so that TIMESYNC does not fight the new time signal.

SFT III Considerations

While the I/O engine of an SFT III server generally isolates hardware dependencies it does not isolate the timer interrupt. Thus, both engines service the timer interrupts independently. However, the SetSyncClockFields call will update the synchronized clock structures in both engines. This greatly simplifies time synchronization on SFT III servers.

Conclusion

Using the interface to the synchronized clock structure in a NetWare 4.0 server, an NLM can discover useful information about the time environment on a server and also control the tick rate of the clock. It is possible to duplicate or augment the function of the TIMESYNC NLM through that interface.

Listing 1: Structures, constants, and prototypes.

typedef LONG unsigned long    /*long is 4 bytes */

typedef struct Synchronized_Clock

{ 

 LONG clock[2];   /* [0] = whole seconds, [1] = fractional - UTC*/

 LONG statusFlags;  /*bit fields as defined below */ 

 LONG adjustmentCount; 

 long adjustmentValue[2];  /* [0] = whole seconds, [1] = fractional*/

 long grossCorrection[2];  /* [0] = whole seconds, [1] = fractional*/

 long tickIncrement[2];  /* [0] = whole seconds, [1] = fractional*/

 LONG stdTickIncrement[2]; /* [0] = whole seconds, [1] = fractional*/

 long eventOffset[2];   /* [0] = whole seconds, [1] =Timesync flags*/

 LONG eventTime;  /* whole seconds only */ 

 LONG daylight;   /* 0 if DST name was not in time zone information */ 

 long timezoneOffset; /* seconds to UTC;LocalTime+timezone = UTC*/ 

 long tzname[2];  /* offset to normal and daylight  names in timezoneString*/ 

 char timezoneString[80]; 

 long daylightOffset; /* seconds of additional offset during Daylight Sav Time*/ 

 long daylightOnOff; /* 0 = not in daylight savings time (OFF), nonzero = ON */ 

 LONG startDSTime;  /* Next time DST starts*/ 

 LONG stopDSTime;  /* Next time DST ends*/

} Synchronized_Clock_T;

/*definitions for bitmask when getting and setting fields*/

#define  SYNCCLOCK_CLOCK_BIT              0x00000001L

#define  SYNCCLOCK_TICK_INCREMENT_BIT     0x00000002L

#define  SYNCCLOCK_ADJUSTMENT_BIT         0x00000004L

#define  SYNCCLOCK_GROSS_CORRECTION_BIT   0x00000008L

#define  SYNCCLOCK_ADJUSTMENT_COUNT_BIT   0x00000010L

#define  SYNCCLOCK_STATUS_BIT             0x00000020L

#define  SYNCCLOCK_STD_TICK_BIT           0x00000040L

#define  SYNCCLOCK_EVENT_TIME_BIT         0x00000080L

#define  SYNCCLOCK_EVENT_OFFSET_BIT       0x00000100L

#define  SYNCCLOCK_HARDWARE_CLOCK_BIT     0x00000200L

#define  SYNCCLOCK_RESERVED1_BIT          0x00000400L

#define  SYNCCLOCK_DAYLIGHT_BIT           0x00000800L

#define  SYNCCLOCK_TIMEZONE_OFFSET_BIT    0x00001000L

#define  SYNCCLOCK_TZNAME_BIT             0x00002000L

#define  SYNCCLOCK_TIMEZONE_STR_BIT       0x00004000L

#define  SYNCCLOCK_DAYLIGHT_OFFSET_BIT    0x00008000L

#define  SYNCCLOCK_DAYLIGHT_ON_OFF_BIT    0x00010000L

#define  SYNCCLOCK_START_DST_BIT          0x00020000L

#define  SYNCCLOCK_STOP_DST_BIT           0x00040000L

/*Bit masks used with clock status*/

#define  CLOCK_IS_SYNCHRONIZED            0X00000001L

#define  CLOCK_IS_NETWORK_SYNCHRONIZED    0x00000002L

#define  CLOCK_SYNCHRONIZATION_IS_ACTIVE  0x00000004L

#define  CLOCK_STATUS_SERVER_TYPE         0x00000F00L

/*function prototypes*/

void  GetSyncClockFields(LONG bitMask, Synchronized_Clock_T *);

void  SetSyncClockFields(LONG bitMask, Synchronized_Clock_T *);

Listing 2: Cooperating with TIMESYNC.

SetTickIncrement(long value)

{ 

  /* Sets the tick increment and notifies TIMESYNC that something changed . */ 

  /* Cancels any pending adjustments. */

  /* Parameter value is a fractional second -  the whole seconds will be set to zero. 

*/

  long mask; 

  Synchronized_Clock_T   aclock;



  /*  Be sure to save the status bits we aren't changing */ 

  GetSyncClockFields(SYNCCLOCK_STATUS_BIT,&aclock);&
  aclock.statusFlags &= (~CLOCK_IS_NETWORK_SYNCHRONIZED);  &
     /*   Notifies TIMESYNC of changes */

 aclock.tickIncrement [0] = 0;         /* Clear whole seconds */ 

 aclock.tickIncrement [1] = value;     /* Set fractional seconds */ 

 aclock.adjustmentCount = 0;    /* Stop applying old adjustment */



/* Copy the new values into the synchronized clock structure */ 

mask = SYNCCLOCK_STATUS_BIT  | SYNCCLOCK_TICK_INCREMENT_BIT

                             | SYNCCLOCK_ADJUSTMENT_COUNT_BIT; 

SetSyncClockFields(mask, &aclock);}&

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates