home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
nisttime.carsoncity.k12.mi.us
/
nisttime.carsoncity.k12.mi.us.tar
/
nisttime.carsoncity.k12.mi.us
/
pub
/
autolock
/
autolock.c
< prev
next >
Wrap
C/C++ Source or Header
|
2003-12-09
|
61KB
|
1,519 lines
#include <stdio.h>
#include <math.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <string.h>
#include <unistd.h>
#include "sizint.h"
#include "autolock.h"
/*
the following global variables define the operation of
the server. they are initialized from the parameter file
during the cold-start procedure. If they are not defined
in the parameter file then the values specified here are
used by default.
*/
int sigma_desired; /*the desired time performance in ms. */
int ncalmax = NCALMAX; /*the maximum number of ntp messages each cycle*/
int ncalmin = 3; /*the minimum number of ntp messages each cycle*/
int intvlmin = 1000; /*the minimum interval between cycles*/
int intvlmax = 80000; /*the maximum interval between cycles*/
int opmode; /*the initial mode of the program */
int ncal= 5; /*initial number of ntp messages on each cycle*/
int intvl= 2000; /*initial interval between cycles*/
int evalint= 259200; /*default is 3 days in seconds*/
int tconf= 12000; /*time constant (in seconds) for ybar update*/
int ttot= 0; /*total running time in seconds*/
#ifdef KERNEL /*if jl version of kernel routines available*/
int kernel= 1; /*default is to use kernel routines if possible*/
struct timex tmx; /*structure for linkage to kernel routines*/
#endif /*KERNEL*/
#ifdef LINUX
struct timex tmx; /*used for adjstimex routine in Linux*/
#endif /*LINUX*/
/*
opmode = 1 means characterize the network link and
compare it to the noise in the local clock
itself. exit after running for evalint seconds.
opmode =2 means change to lock mode after the evaluation
is completed or else do a warm re-start and
switch to lock mode immediately using the parameters
from a previous evaluation.
*/
main(int argc,char *argv[])
{
/*
function prototypes
*/
int getdif(char [],struct autolockst *,int *);
/*gets time difference local-server*/
int sw(int, char* [],char *, long int *); /*parses command line*/
void getautolock(FILE **, struct autolockst *); /*reads data from file*/
void putautolock(FILE **, struct autolockst[]); /*writes data to file*/
void putmsg(char *); /*writes message to log file*/
void pushautolock(struct autolockst[],int,int); /*pushes down data stack*/
int sett(double); /*steps time in one step using settimeofday*/
int adjt(double); /*adjusts time by slewing clock*/
double adev(double [],int ,int); /*computes Allan deviation */
char serv[NUMSRV][20]; /*addresses of primary and alternate servers*/
int i,j;
int j1,j2;
int nhr,nmin; /*time of next cycle*/
long int v; /*values returned from command line switch*/
char s; /*command-line switch letter */
int debug = 0; /*flag for debug mode, default is off*/
extern int errno; /*error number from the system*/
FILE *pop; /*used to read from parameter file*/
FILE *lop; /*used to read from state file*/
struct autolockst autolock[NUMSTATES]; /*push-down stack of states*/
int numsrv = 0; /*number of servers defined, up to NUMSRV*/
char errmsg[100]; /*holds error message text*/
char keywoid[30]; /*holds keyword from parameter file*/
char keyval[30]; /*holds value from parameter file*/
double fract; /*time constant parameter*/
int ntry; /*number of consecutive calls to getdif*/
int ntry2; /*number of consecutive jitter failures*/
int ntry3; /*number of consecutive prediction failures*/
int fstsrv; /*index of server to try first*/
int altsrv; /*number of alternate servers we have tried*/
double alterr[NUMSRV]; /*time diff from alternate servers*/
double altrms[NUMSRV]; /*rms of time differences from alternates*/
int filefull= NUMSTATES; /*counter to see when file is getting full*/
char filenam[80]; /*used in the file rename department*/
int pid; /*return value from wait call*/
union wait *status; /*return status from wait call*/
int eval_elap= EVALPERIOD; /*interval since last performance evaluation*/
int tloop2; /*number of calibration cycles in mode 2*/
int tloop3; /*number of calibration cycles in mode 3*/
int tloop4; /*number of cycles in mode 4*/
char *cp; /*pointer to command line parameter*/
double tcor; /*correction in integer seconds for sett*/
double terr; /*absolute value of x*/
double tsig; /*standard deviation of multiple servers*/
double ddum; /*temporary double*/
int linno; /*line number of configuration file*/
double y[NUMSTATES]; /*frequency estimates for Avar calculation*/
int numby; /*pointer to next free location in y array*/
double dtemp; /*temporary double*/
int itemp; /*temporary integer*/
double avar[10]; /*Allan deviation as function of lag*/
int nvar; /*index into avar array*/
int jwait; /*time between periodic corrections*/
int jloop; /*number of periodic corrections per cycle*/
/*
lsl and lsr give the local and remote notions of the leap
second flag (respectively). lsr is normally set by getdif
based on a message from a server; this change is normally
propagated into lsl. What happens next depends on whether
or not the kernel knows how to do a leap second by itself.
see the comments below.
*/
int lsl= 0; /*local notion of the leap second flag*/
int lsr=0; /*server notion of the leap second flag*/
while( sw(argc,argv,&s,&v) != 0) /*decode command line switches*/
{
switch (s)
{
case 'd': /*turn on debug mode*/
debug=1;
fprintf(stderr,"\n Entering debug mode.");
break;
default:
fprintf(stderr,"\n Switch %c not recognized.\n",s);
exit(-1);
}
argc--; /*one less parameter*/
argv++; /*advance to next parameter*/
}
/*
if debug is not defined, then we will run in normal mode.
first convert ourselves into a daemon.
We enter the phone booth as mild-mannered Clark Kent,
we make a copy of ourselves, the original disappears,
the child emerges and then ....
*/
if(debug == 1) goto getparam; /*remain a normal program in debug */
signal(SIGTTOU,SIG_IGN); /*the keyboard can't stop us*/
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
if( (i=fork()) < 0) abort(); /*fork of child failed*/
if(i > 0)
{
printf("\n pid of process is %d",i);
exit(0); /*the parent exits */
}
/*
make ourselves into a unique process group so that we
can't re-acquire a control terminal that might be able
to stop us.
Then close our control terminal.
Nothing can stop us now ...
*/
#ifdef LINUX
if( setpgid(0,getpid() ) == -1) abort();
#else /*LINUX*/
if( setpgrp(0,getpid() ) == -1) abort();
#endif /*LINUX*/
if( (i= open("/dev/tty",O_RDWR) ) >= 0)
{
ioctl(i,TIOCNOTTY,(char *) NULL); /*bye bye terminal*/
close(i);
}
for(i=0; i< NOFILE; i++) close(i); /*close all files*/
errno=0; /*reset errors, if any*/
/*
now get the default parameters for the process
*/
getparam:
/*
change to default directory, open parameter file and
get parameters.
*/
if(chdir(BINDIR) < 0)
{
sprintf(errmsg,"Oops - change directory to %s failed.\n",BINDIR);
putmsg(errmsg);
exit(-1);
}
if( (pop=fopen(PNAME,"r")) == NULL) /*open read only*/
{
sprintf(errmsg,"Oops -- can not open parameter file %s\n",PNAME);
putmsg(errmsg);
exit(-1);
}
/*
the parameter file is a free-form file of the form:
keyword value <newline>
if the keyword at the start of a line is # then the line is
treated as a comment. if there is a keyword but it is not
recognized then the line is treated as a comment and a
comment is written to the log file.
the keyword comparison is done in lower case, and upper
case keywords are converted before comparing them.
*/
linno=0; /*reset line counter*/
readparam:
linno++; /*increment line counter*/
if(fscanf(pop," %s",keywoid) == EOF) goto finparam;
if(keywoid[0] == 043) goto nxtlin; /* # starts a comment line*/
if(fscanf(pop," %s",keyval) != 1) goto fmterr;
for(i=0; keywoid[i] != '\0'; i++)
keywoid[i]=tolower( (int) keywoid[i]);
if(strncmp(keywoid,"sigma",5) == 0)
{
if(sscanf(keyval," %d",&sigma_desired) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"nmax",4) == 0)
{
if(sscanf(keyval," %d",&ncalmax) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"nmin",4) == 0)
{
if(sscanf(keyval," %d",&ncalmin) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"tmin",4) == 0)
{
if(sscanf(keyval," %d",&intvlmin) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"tmax",4) == 0)
{
if(sscanf(keyval," %d",&intvlmax) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"tzero",5) == 0)
{
if(sscanf(keyval," %d",&intvl) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"mode",4) == 0)
{
if(sscanf(keyval," %d",&opmode) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"server",6) == 0)
{
if(numsrv >= NUMSRV) goto nxtlin; /*server list is full*/
if(sscanf(keyval," %s",&serv[numsrv][0]) != 1) goto fmterr;
numsrv++; /*total number of servers we know about*/
goto nxtlin;
}
if(strncmp(keywoid,"evalt",5) == 0)
{
if(sscanf(keyval," %d",&evalint) != 1) goto fmterr;
goto nxtlin;
}
if(strncmp(keywoid,"tconf",5) == 0)
{
if(sscanf(keyval," %d",&tconf) != 1) goto fmterr;
goto nxtlin;
}
#ifdef KERNEL
/*
the keywoid "kernel" will only be recognized if
KERNEL is defined. otherwise, this code will not be
present and it will be treated as a format error.
*/
if(strncmp(keywoid,"kernel",6) == 0)
{
if(sscanf(keyval," %d",&kernel) != 1) goto fmterr;
goto nxtlin;
}
#endif /*KERNEL*/
fmterr: /*format error or did not recognize keyword*/
sprintf(errmsg,
"Format error reading line %d starting with %s",
linno,keywoid);
putmsg(errmsg);
nxtlin: /*skip remainder of line, go back and read next one*/
while( (s=getc(pop)) != '\n') if(s == EOF) goto finparam;
goto readparam;
finparam: /*done with the parameter file*/
fclose(pop); /*finished reading parameters*/
/*
now open state file and read the most recent states, which
are the first ones in the file. The read ends either when
NUMSTATES have been read in or when a state with a day number
of 0 is found. This can also happen when the physical EOF
is reached, but normally there should be an explicit record
with a day number of 0. If fewer than NUMSTATES are read
from the file, then the remaining entries are set to 0
by the following while loop.
note that the most recent state with index 0 is not read
by this process. It will be changed in response to the
getdif request.
*/
if( (lop=fopen(SNAME,"r")) == NULL) /*open read only*/
{
sprintf(errmsg,"Oops -- error opening state file %s.\n",SNAME);
putmsg(errmsg);
exit(-1);
}
for(i=1; i < NUMSTATES; i++)
{
getautolock(&lop,&autolock[i]);
if(autolock[i].day == 0) break; /*logical end of data*/
}
fclose(lop);
while(i < NUMSTATES) autolock[i++].day = 0; /*set remaining ones to 0*/
/*
if the most recent state read from the file does not have an MJD
value of 0, then transfer the static parameters from that state
to the current state, which is number 0
there is no point in transferring those parameters that will
be changed during the call to getdif below.
note that the time constant for the frequency filter is
copied from the existing state vector, and that this value
may not be the same as the value read from the initial parameter
file or the default value in the program definition. This version
of the software may adjust fcon based on the results of an Allan
variance calculation -- see the call to adev below.
what happens next depends on opmode. if opmode = 1 then we
are just trying to evaluate the local clock, and we are going
to start over again as if this was a cold start even though
we might have read previous data from the file.
if opmode == 2 then we are going to proceed to a lock state.
advance to mode 4 if the previous mode is 3 -- i.e., lock the
clock if we have read a previous evaluation. otherwise go
back and do a complete cold-start evaluation.
*/
if(autolock[1].day != 0)
{
autolock[0].mode=autolock[1].mode;
autolock[0].rms_avg=autolock[1].rms_avg;
autolock[0].d= autolock[1].d; /*normally set to 0*/
intvl=autolock[0].waitn= autolock[1].waitn;
autolock[0].ybar= autolock[1].ybar;
autolock[0].fcon= autolock[1].fcon;
autolock[0].sumerr= autolock[1].sumerr;
if( (opmode == 1) || /*either we are just evaluating, or*/
(autolock[0].mode < 3) ) /*lock coming but eval not finished*/
{
autolock[0].mode= 0; /*then go back and do a cold start*/
autolock[0].waitn= intvl;
} /*end of opmode == 1 or mode < 3*/
else
{
autolock[0].mode= 4; /*opmode ==2 and mode >= 3*/
tloop4= 0; /*initialize loop counter for mode 4*/
} /*end of opmode != 1 and mode >= 3*/
} /*end of read a day number != 0*/
else /*day number == 0 means absolute cold start*/
{
autolock[0].waitn=intvl;
autolock[0].mode= 0;
} /*end of absolute cold start*/
#ifdef KERNEL
/*
if the kernel routines are available and if they have been
selected, then reset all of the kernel parameters to 0,
and set the health to bad.
note that MOD_LOCKCLOCK must always be specified in the jl
version or else a request to change anything will be ignored.
the expected return status is TIME_ERROR because we have
just set the clock to unsynchronized. if we don't get this,
then there is something wrong with the linkage to the
kernel.
*/
if(kernel == 1) /*kernel routines selected*/
{
tmx.modes= MOD_LOCKCLOCK | MOD_STATUS | MOD_OFFSET | MOD_FREQUENCY |
MOD_MAXERROR | MOD_ESTERROR | MOD_TIMECONST |
MOD_DST | MOD_HEALTH | MOD_LS;
tmx.status=STA_UNSYNC; /*we start out un synched*/
tmx.offset=tmx.freq=tmx.maxerror=tmx.esterror=tmx.constant=0;
tmx.dst=tmx.ls=0;
tmx.health=3; /*and un healthy*/
j=syscall(SYS_ntp_adjtime,&tmx); /*tell it to the kernel */
if(j != TIME_ERROR)
{
sprintf(errmsg,
"103-return status from syscall is %d, expected=%d",
j,TIME_ERROR);
putmsg(errmsg);
} /*end of return status not as expected*/
} /*end of kernel routines selected*/
#endif /*KERNEL*/
#ifdef LINUX
tmx.modes= ADJ_OFFSET | ADJ_FREQUENCY | ADJ_MAXERROR |
ADJ_ESTERROR | ADJ_STATUS | ADJ_TIMECONST;
tmx.offset=tmx.freq=tmx.maxerror=tmx.esterror=0;
tmx.constant=tmx.precision=tmx.tolerance=0;
tmx.status= TIME_BAD;
j=adjtimex(&tmx);
if(j != TIME_BAD)
{
sprintf(errmsg,
"103-return status from adjtimex is %d, expected=%d",
j,TIME_BAD);
putmsg(errmsg);
}
#endif /*LINUX*/
/*
now we are ready for serious stuff.
infinite loop of main code begins here.
each time we make another estimate of the frequency,
we save the value in array y, and numby is the index
into this array. the index is reset if the value
of waitn changes so that the array contains only
values at a constant value of waitn.
these data are then used to calculate an estimate of
Avar once a day or so. this estimate is only approximate
since it will not be robust if there have been a lot
of errors during the day. There is no good way of dealing
with errors since at the very least they introduce gaps
into the array. When the clock is free-running during the
evaluation phase, the avar estimate is used to estimate a
value for fcon, the time-constant for the frequency estimator.
Since this is the only purpose in this version, there is not
much point in calculating the avar if the clock is locked.
*/
numby= 0; /*initialize the array index */
while(1)
{
/*
query the primary server (the first one). If the response is
+1 then the operation was okay and the subroutine has stored the
time difference local-server in the x parameter of the 0th autolock
record in units of seconds.
in addition the subroutine has filled in the MJD of the
comparison in autolock[0].day, the time in milliseconds since
0000 UTC in autolock[0].fday and the rms in units of seconds.
the nval parameter is set to the number of responses that were
actually received from the server.
if the return status is anything else then something is wrong with
that server. Either there is something wrong with its address, it
is unreachable, it does not respond or it is not healthy. See
getdif for details. Try the next server (if there is one) in
any of these cases. If we run out of servers then the status
of the last one is the one that is used below.
*/
ntry=ntry2=ntry3=0; /*initialize number of consecutive tries*/
fstsrv= 0; /*point to 0th server first*/
altsrv= 0; /*no alternate servers yet*/
gogetem:
/*
if the kernel routine support is available and if using
those routines is enabled, then esterr is set to 1 to
show that a calibration cycle is in progress and maxerr
starts a watch dog timer.
reset esterr as soon as we return from getdif to show
that we are not hung up there.
*/
#if KERNEL == 1
if(kernel == 1)
{
tmx.modes= MOD_LOCKCLOCK | MOD_MAXERROR | MOD_ESTERROR;
tmx.maxerror= -10240l; /* -20*512 => 20 s*/
tmx.esterror=1;
syscall(SYS_ntp_adjtime,&tmx); /*tell the kernel*/
} /*end kernel == 1*/
#endif /*KERNEL*/
/*
start a loop over all of the time servers that we
know about. the loop starts from fstsrv (which is
normally 0) and continues through all numsrv systems.
the operation ends with the data from the first time
server that advertises itself as healthy. if we are
currently in locked mode, then the lock loop may change
fstsrv if it looks like there is a consistency problem.
see below ...
*/
for(j=fstsrv; j<numsrv; j++)
if( (i=getdif(&serv[j][0],&autolock[0],&lsr)) == 1) break;
#if KERNEL == 1
if(kernel == 1)
{
tmx.modes= MOD_LOCKCLOCK | MOD_ESTERROR;
tmx.esterror= 0;
syscall(SYS_ntp_adjtime,&tmx);
} /*end kernel == 1*/
#endif /*KERNEL*/
/*
when we get here, we queried server number j and
received status i. If i is negative then we couldn't
get anything from any server. wait a while and try
again. If this doesn't work after 5 times (waiting
a bit longer each time), then something is really
broken. So write out the current state and wait
for next time using a shorter interval than the
standard waitn seconds.
*/
autolock[0].srv=j; /*store server number */
autolock[0].flags=i; /* and state of that request*/
if(i < 1) /*error return for all servers*/
{
if(ntry++ > 5)
{
intvl= 600;
goto finfin; /*give up*/
}
sleep(15 + 15*ntry); /*wait progressively longer*/
goto gogetem; /*go try again*/
}
/*
if the previous mode is 0 then this is a cold start
just store the time difference and go around again.
*/
if(autolock[0].mode == 0)
{
autolock[0].mode = 1; /*advance to mode 1*/
autolock[0].rms_avg=autolock[0].rms;
autolock[0].d= 0; /*frequency aging turned off*/
autolock[0].fcon= tconf; /*time constant for ybar*/
intvl=autolock[0].waitn; /*standard interval*/
goto finfin; /*finished with this cycle*/
} /*end of processing in mode 0*/
/*
if we are still here then this is not the first cycle following
a complete cold start. compute the actual elapsed time since the
last cycle and use this to compute the frequency of the local
clock with respect to the calibration source. this estimate
is simple if the clock is free-running and has not been disturbed
since the last cycle.
this estimate will not be correct if the local clock has
been adjusted in some way since the last cycle -- if the
system has been re-booted, for example. For the same
reason, this estimate will not be correct when we are in any
of the lock modes, since the clock is being adjusted
all of time time there.
if we are in mode 5 then the clock is being adjusted
using the current average frequency ybar. the effective
frequency during the last interval is then with respect to
ybar.
*/
autolock[0].waitx= 86400*(autolock[0].day-autolock[1].day)
+ 0.001*(autolock[0].fday - autolock[1].fday);
if(autolock[0].mode < 4) /*clock is guaranteed free-running*/
autolock[0].y=(autolock[0].x - autolock[1].x)/autolock[0].waitx;
else if(autolock[0].mode == 4) /*if adjustment in progress */
autolock[0].y=autolock[0].ybar; /*then lock the frequency*/
else autolock[0].y= autolock[0].ybar +
autolock[0].x/autolock[0].waitx;
/*
if this is mode 1 then this is the second cycle following
a cold start. we have already estimated the frequency above,
so initialize the average frequency to the value we just
got. At this point, there is no way of knowing if the
data are consistent since we don't have any idea of the
rate offset of the local clock.
use the average of the rms on the two cycles for rms_avg.
*/
if(autolock[0].mode == 1)
{
autolock[0].mode=2;
autolock[0].sumerr= 0;
autolock[0].ybar= autolock[0].y;
autolock[0].rms_avg=
0.5*autolock[0].rms + 0.5*autolock[1].rms_avg;
tloop2= 0; /*initialize counter for mode 2*/
intvl=autolock[0].waitn; /*standard interval*/
y[numby++]=autolock[0].y; /*save this freq. estimate*/
goto finfin; /*that's all for this round*/
} /*end of processing in mode 1*/
/*
if we are still here then there have been at least two previous
cycles since the cold start, and we have used those values to
compute an initial estimate of the frequency offset of the local
clock with respect to the calibration source.
1. look at the rms of these measurements and reject this
group if the rms is much larger than the last two times.
2. use this average value to compute the prediction error
over this interval.
3. continue to average the frequency value for the number of
loops given by the time constant fcon.
4. compute the average of the rms measurement noise using
a filter with a 12 hour time constant.
note that glitches at this point are not rejected -- they simply
are included in the value for ybar and for rms.
thus the primary purpose of mode 2 is to get an estimate for
ybar and for the rms noise of the link. We should have a
reasonable estimate of ybar in fcon seconds, so switch
to mode 3 after that.
*/
if(autolock[0].mode == 2)
{
if(autolock[0].rms > 8*autolock[1].rms_avg) /*very noisy*/
{
if(ntry2++ > 5)
{
autolock[0].rms_avg= 2*autolock[1].rms_avg;
autolock[0].flags= 2; /*show rms increase*/
intvl= 600;
goto finfin; /*wait for next time*/
} /*end of the fifth try*/
sleep(15+ 15*ntry2); /*wait progressively longer*/
goto gogetem; /*and then try again*/
} /*end of current rms much larger than average*/
/*
compute the time prediction error. this is the
differnce between the actual frequency measured over
the most recent interval in y and the low-passed
average value in ybar. this frequency difference is
converted to a time dispersion by multiplying by
waitx, which is the actual elapsed time over the
most recent time interval.
*/
autolock[0].errx=
fabs(autolock[0].y-autolock[1].ybar)*autolock[0].waitx;
/*
now update ybar and sumerr using an exponential filter with
a time constant of fcon seconds. the scaling value for this
filter is waitx/fcon -- the ratio of the actual elapsed
time over the last interval to the time constant. both
parameters are in seconds.
since waitx is an adjustable parameter, it might
eventually exceed fcon. Do not allow the value of
fract to exceed 0.4 no matter what the delay is.
note that this strategy can push the effective value of
the time constant to values that may be larger than the
optimum value given the actual Allan deviation of this
clock. However, the alternative is allow fract to be 1
or greater, which makes the update more sensitive to
frequency glitches. You pays your money ...
*/
y[numby++]= autolock[0].y; /*save this value*/
fract= ((double)autolock[0].waitx)/((double)autolock[0].fcon);
if(fract > 0.4) fract= 0.4;
autolock[0].ybar= (1-fract)*autolock[0].ybar +
fract*autolock[0].y;
autolock[0].sumerr= (1-fract)*autolock[0].sumerr +
fract*autolock[0].errx;
fract= ((double)autolock[0].waitx)/432e+2; /*12 hr time const.*/
/*
as above, do not alow the value of the time constant
to exceed 1 even if the delay becomes larger than
12 hours (which actually isn't very likely).
*/
if(fract > 1) fract= 1;
autolock[0].rms_avg=
(1-fract)*autolock[1].rms_avg +
fract*autolock[0].rms;
tloop2 += autolock[0].waitx; /*running time in mode 2*/
if(tloop2 > autolock[0].fcon) /*then switch to mode 3*/
{
autolock[0].mode=3;
tloop3= 0;
}
intvl= autolock[0].waitn;
goto finfin; /*finished for this round*/
} /*end of processing in mode 2*/
/*
the primary purpose of mode 3 is to continue to get
a better estimate of ybar and to start using it to
look at the prediction error in a serious way.
in mode 2 we calculate the prediction error but don't
do anything we these estimates beyond averaging them.
here we are going to get serious and reject a measurement
if it is not consistent with the average of the prediction
error we have been estimating, which is being accumulated
in sumerr.
note that we have two noise parameters: rms, which measures
the scatter in a closely-spaced group of measurements and
errx, which measures the prediction error of the algorithm
based on the current value of ybar. Both of these parameters
are averaged -- rms into rms_avg and errx into sumerr.
*/
if(autolock[0].mode == 3)
{
if(autolock[0].rms > 4*autolock[1].rms_avg)
{
if(ntry2++ > 5)
{
autolock[0].rms_avg= 2*autolock[1].rms_avg;
autolock[0].flags= 2; /*show rms increase*/
intvl= 600;
goto finfin; /*wait for next time*/
} /*end of the fifth try*/
sleep(15+ 15*ntry2); /*wait progressively longer*/
goto gogetem; /*and then try again*/
} /*end of current rms larger than the average*/
fract= ((double)autolock[0].waitx)/432e+2;/*12 hr time const.*/
if(fract > 1) fract= 1;
autolock[0].rms_avg=
(1-fract)*autolock[1].rms_avg +
fract*autolock[0].rms;
autolock[0].errx=
fabs(autolock[0].y-autolock[1].ybar)*autolock[0].waitx;
if(autolock[0].errx > 5*autolock[0].sumerr)
{
if(ntry3++ > 5)
{
autolock[0].sumerr = 2*autolock[1].sumerr;
autolock[0].flags=3; /*show data noisy*/
intvl= 600;
goto finfin; /*give up*/
}
sleep(15 + 15*ntry3); /*wait progressively longer*/
goto gogetem; /*go try again*/
} /*end of prediction error very noisy*/
y[numby++]= autolock[0].y; /*save this value*/
fract= ((double)autolock[0].waitx)/((double)autolock[0].fcon);
if(fract > 0.4) fract= 0.4;
autolock[0].ybar= (1-fract)*autolock[0].ybar +
fract*autolock[0].y;
autolock[0].sumerr= (1-fract)*autolock[0].sumerr +
fract*autolock[0].errx;
eval_elap -= autolock[0].waitx;
tloop3 += autolock[0].waitx; /*time in mode 3*/
/*
if opmode is set to 2 then switch to mode 4 after
fcon seconds in mode 3. if opmode is something else
then stay in mode 3 forever.
*/
if( (opmode == 2) && (tloop3 > autolock[0].fcon) )
{
autolock[0].mode =4;
tloop4= 0; /*initialize loop counter for mode 4*/
} /*end of time to switch to mode 4*/
intvl=autolock[0].waitn;
goto finfin; /*that's all for this round*/
} /*end of mode 3*/
if(autolock[0].mode == 4) /*adjust clock to be on time*/
{
/*
first check to see if the rms scatter in the current
group is reasonable compared to previous values.
if it is not then wait a bit and go around again
up to 5 times. if that still doesn't work then
increase the average value of the rms and wait
a while before trying again.
if the current scatter is reasoanble then set the
clock to be on time. this is done in a single step
if the time offset is large and by a slew if the offset
is small. If the offset is large, it will probably
take 2 rounds of this code to get it right -- the first
round will take out the big part of the offset and the
second round will take out the remainder.
note that the slew correction is always made using
a fixed delay of 10 minutes and that the offset of
the local clock is accounted for during this time.
*/
if(autolock[0].rms > 4*autolock[1].rms_avg)
{
if(ntry2++ > 5)
{
autolock[0].rms_avg= 2*autolock[1].rms_avg;
autolock[0].flags= 2; /*show rms increase*/
intvl= 600;
goto finfin; /*wait for next time*/
} /*end of the fifth try*/
sleep(15+ 15*ntry2); /*wait progressively longer*/
goto gogetem; /*and then try again*/
} /*end of current rms larger than the average*/
/*
if this is the first time in mode 4 then the clock
has been free-running up to this point and we can
check the data using the usual errx test. we can't
do this after the first time because we are adjusting
the clock then and ybar is no longer a good estimator
of the average frequency over this interval.
*/
if(tloop4 == 0) /*first time in mode 4*/
{
autolock[0].errx=
fabs(autolock[0].y-autolock[1].ybar)*autolock[0].waitx;
if(autolock[0].errx > 5*autolock[0].sumerr)
{
if(ntry3++ > 5) /*give up, too many tries*/
{
autolock[0].sumerr = 2*autolock[1].sumerr;
autolock[0].flags=3; /*show data noisy*/
intvl= 600;
goto finfin; /*give up*/
} /*end of give up, too many tries*/
sleep(15 + 15*ntry3); /*wait progressively longer*/
goto gogetem; /*and go try again*/
} /*end of prediction error very noisy*/
} /*end of first time in mode 4*/
tloop4 += autolock[0].waitx; /*actual time in mode 4*/
terr= fabs(autolock[0].x); /*absolute value of offset*/
/*
if the kernel mods are supported and enabled then change
the health to 2 at this point -- getting better.
*/
#ifdef KERNEL
if(kernel == 1)
{
tmx.modes= MOD_LOCKCLOCK | MOD_HEALTH;
tmx.health=2;
syscall(SYS_ntp_adjtime,&tmx);
}
#endif /* KERNEL*/
/*
if the time offset of the clock is large then set it
in one shot using a time step. This may not work
to better than 1 tick, and so we may be left with
a small time error. go around again in mode 4 in
this case to check on this.
*/
if(terr >= 0.5) /*step clock for large time offset*/
{
tcor= -autolock[0].x;
j=sett(tcor);
if(j != 0) /*error trying to set clock*/
{
sprintf(errmsg,"Error %d setting clock by sett.",j);
putmsg(errmsg);
break;
}
intvl=120; /*wait just a short time*/
goto finfin; /*and go around again*/
}
/*
if the time error is significant in the sense that it is
larger than the average rms of the measurement noise but
at the same time is small enough to be removed by a clock
slew rather than a step then make a final adjustment
using the clock slew system. use a fixed delay of 600s
for this, and include the fact that the clock will drift
because of its frequency offset during this adjustment
period.
*/
if(terr >= 2*autolock[0].rms_avg)/*small but significant*/
{
tcor= -autolock[0].x -600*autolock[0].ybar;
j=adjt(tcor);/*slew clock for small but significant offset*/
if(j != 0)
{
sprintf(errmsg,"Error %d adjusting clock by adjt.",j);
putmsg(errmsg);
break;
}
intvl= 600;
/*
if the kernel mods are supported and enabled then change
the health to 1 at this point -- getting better.
*/
#ifdef KERNEL
if(kernel == 1)
{
tmx.modes= MOD_LOCKCLOCK | MOD_HEALTH;
tmx.health=1;
syscall(SYS_ntp_adjtime,&tmx);
}
#endif /* KERNEL*/
/*
if we have been adjusting the clock for some
time now and we are still here, then it is
probable that the frequency that we are using
is not quite right. This will never be fixed
in this mode, since we don't update the frequency
here. fall through in this case and switch to mode 5.
this is most likely to happen when the rms_avg is
very small, since even a small time offset will
be treated as "significant" by the test above.
*/
if(tloop4 < 3*autolock[0].waitn) goto finfin;
} /*end of small but significant*/
/*
finally, switch to the mode 5 lock system if the clock time
offset is small enough that it is no longer statistically
significant.
although we have just set the clock, it is going to
start drifting off again at a rate of ybar. since we
are not yet controlling the frequency of the clock,
we are going to find an error on the next round of
intvl*ybar, where intvl is the length of time we
wait. choose intvl so that this dispersion will on the
order of the average measurement error which is rms_avg,
but not more than 10 minutes.
*/
autolock[0].mode= 5; /*switch to locked mode*/
eval_elap= EVALPERIOD; /*reset evaluation timer*/
intvl= fabs(autolock[0].rms_avg/autolock[0].ybar);
if(intvl > 600) intvl= 600;
/*
do not let the interval between queries become too
small even if the rms error goes to 0
*/
if(intvl < intvlmin) intvl= intvlmin;
goto finfin; /*done with this round*/
} /*end of processing in mode 4 -- set clock to be on time*/
/*
when we get to mode 5, we are going to use the calibration
data to keep the local clock locked. The first step is
to perform the usual sanity checks on the data.
*/
if(autolock[0].mode == 5) /*maintain lock*/
{
/*
if the rms of the current group of measurements
is much larger than the average rms then go around
and try again, waiting a little bit longer each time.
if the current rms is consistent with the previous
values, then update the rms with a 12-hour time
constant as above.
*/
if(autolock[0].rms > 4*autolock[1].rms_avg)
{
if(ntry2++ > 5)
{
autolock[0].rms_avg= 2*autolock[1].rms_avg;
autolock[0].flags= 2; /*show rms increase*/
intvl= 600;
goto finfin; /*wait for next time*/
} /*end of the fifth try*/
sleep(15+ 15*ntry2); /*wait progressively longer*/
goto gogetem; /*and then try again*/
} /*end of current rms larger than the average*/
fract= ((double)autolock[0].waitx)/432e+2;/*12 hr time const.*/
if(fract > 1) fract= 1;
autolock[0].rms_avg=
(1-fract)*autolock[1].rms_avg +
fract*autolock[0].rms;
/*
as above, the prediction error is the difference between
the effective freqency over the last interval and
the average frequency, where this frequency difference is
converted to a time by multiplying by the time interval
since the last calibration cycle. The local clock has
been steered by applying ybar to it. since the observed
frequency over the last interval is ybar + x/waitx, the
difference between the two multipled by waitx really reduces
to just x. Another way of saying this is that the predicted
value of x is 0 and the prediction error is therefore whatever
non-zero value we find. note that this will not be true on
the first round through this part, since the local clock
frequency is not controlled yet.
*/
autolock[0].errx= autolock[0].x;
/*
the algorithm splits into two parts at this point. If
the prediction error is larger than the desired performance
level as specified by sigma_desired, then the prediction
error is treated as a problem because the algorithm is
not working as well as was specified. The first question
in this case is whether or not this big prediction error
is a one-time glitch or the real thing. To test for this
possibility, try the whole thing all over again, waiting
a bit between each try. if this doesn't fix the problem
after a number of tries, then more serious analysis is
needed. there are four possibilities:
1. a local time step
2. a local frequency step
3. the distant server is broken.
4. a network glitch
first check possibilities 3 and 4 by switching to
another server, if we know about one and if we
haven't tried it yet. the current data were received
from autolock[0].srv. this will normally point to
server 0, unless that one was unreachable or unhealthy.
set the fstsrv pointer to the one after that if we
know about it, reset the flag and go around again.
save the time difference from the current server in
array alterr and the rms scatter of the measurements
in altrms.
if, on the other hand, the prediction error is not
larger than the desired performance level, then this
is not necessarily a glitch. The prediction error
will always be larger than the rms_avg in this case
because we are pushing the averaging time to longer
values than is optimum based on white-noise considerations,
and the average prediction error will no longer be
a good expectation of the observed data.
note that the units of sigma_desired are in milliseconds,
whereas everything else is in seconds.
*/
if( (fabs(autolock[0].errx) > 5*autolock[0].rms_avg) &&
(1000*fabs(autolock[0].errx) > 3*sigma_desired) )
{
if(ntry3++ > 5) /*looks like it is not going away*/
{
altrms[altsrv] = autolock[0].rms;
alterr[altsrv++]= autolock[0].errx;
fstsrv= autolock[0].srv + 1; /* point to next one*/
if(fstsrv < numsrv) /* it is available*/
{
ntry3= 0; /*reset the loop counter*/
goto gogetem; /*try this one*/
}
/*
when we get here we have gotten a prediction error
using all ofthe servers that we know about. the
array alterr has all of the prediction errors that
we measured and altsrv is the number of them we have
received. if there are several of these, see
if they are consistent. if the data from the different
servers seem to agree then assume we have a time
step. If this is the second time step in a row then
it is more likely a frequency step.
*/
if(altsrv > 1) /*more than one server*/
{
terr= 0; /*initialize the mean */
for(j=0; j < altsrv; j++) terr += alterr[j];
terr /= altsrv; /*average difference*/
tsig= 0; /*intialize the std. dev.*/
for(j=0; j < altsrv; j++) /*compute sum square*/
{
ddum= alterr[j]-terr;
tsig += ddum*ddum;
} /*end of for loop to compute sum square*/
tsig= sqrt(tsig/(altsrv-1)); /*normalize ss to sigma*/
if(fabs(terr) > 2*tsig) /*looks real*/
{
autolock[0].x= terr; /*average value*/
autolock[0].rms= tsig;
autolock[0].flags= 4; /*time step*/
if(autolock[1].flags == 4) autolock[0].flags=6;
goto update; /*adjust local clock*/
} /* end of looks real*/
else /*multiple servers don't agree*/
{
/*
write out all of the time messages for
later debugging.
*/
for(j=0; j<altsrv; j++)
{
sprintf(errmsg,"server %d diff=%le",
j,alterr[j]);
putmsg(errmsg);
}
sprintf(errmsg,"mean = %le var=%le",terr,tsig);
putmsg(errmsg);
/*
if the values from the different servers
don't agree then either one of the distant
servers is broken or else one of the paths has
become very asymmetric so that the delay
estimate has been fooled.
there is nothing we can do about this if
we have only two other servers. set the flag
to show the problem, and wait a bit to
see if the problem gets fixed.
*/
if(altsrv == 2) /*only 2 servers -- tough luck*/
{
autolock[0].flags= 5; /*servers don't agree*/
intvl= 600;
goto finfin; /*wait a while and try again*/
} /*end of only two servers*/
/*
if we are here then we have more than 2 servers
and they don't agree. see if we can find an
outlier. recompute the mean time difference
dropping a different server each time. in
the following loops, we drop server j1 on each
round and then loop j2 over the remaining ones
to compute the mean.
*/
for(j1=0; j1< altsrv; j1++)
{
terr=0; /*initialize the mean value*/
for(j2=0; j2< altsrv; j2++)
{
if(j2 == j1) continue; /*drop this server*/
terr += alterr[j2];
} /*end of loop over remaining ones*/
terr /= altsrv - 1; /*we dropped one*/
tsig= 0; /*intialize the std. dev.*/
for(j2=0; j2 < altsrv; j2++) /*compute sum sq.*/
{
if(j2 == j1) continue; /*drop this one*/
ddum= alterr[j2]-terr;
tsig += ddum*ddum;
} /*end of for loop to compute sum sq*/
tsig= sqrt(tsig/(altsrv-2)); /*ss -> sigma*/
sprintf(errmsg,
"Drop server %d new mean, sigma= %le %le",
j1,terr,tsig);
putmsg(errmsg);
if(fabs(terr) > 2*tsig) /*looks real*/
{
autolock[0].x= terr; /*average value*/
autolock[0].rms= tsig;
autolock[0].flags= 4; /*time step*/
if(autolock[1].flags == 4)
autolock[0].flags=6;
sprintf(errmsg,"drop server %d",j1);
putmsg(errmsg);
sprintf(errmsg," new mean= %le new sig= %le",
terr,tsig);
putmsg(errmsg);
goto update; /*adjust local clock*/
} /*end of looks real*/
/*
if we get to here then the servers don't
agree and dropping one of them didn't seem
to help. looks like that's all for now
folks.
*/
autolock[0].flags= 5;
intvl= 600;
goto finfin; /*wait a while and try again*/
} /*end of loop to drop server j1*/
} /*end of multiple servers dont agree*/
} /*end of more than one server*/
/*
if we get here then either we only know about one
server or we can only find one server that is healthy.
it is not clear who is right in this case, so
do not use these data to update our frequency or our
time estimates. increase the prediction error tolerance,
set the flag to show that the data are noisy and go
around again.
*/
autolock[0].rms_avg= 2*autolock[1].rms_avg;
autolock[0].flags=3; /*show data noisy*/
intvl= 600;
goto finfin; /*give up*/
} /*end of the error persists after 5th try*/
sleep(15 + 15*ntry3); /*wait progressively longer*/
goto gogetem; /*go try again*/
} /*end of prediction error very noisy*/
/*
when we get to update then either the data received on
the current cycle are about what we expected or else
we have confirmed time step because several servers
agree on the time difference.
*/
update:
fract= ((double)autolock[0].waitx)/((double)autolock[0].fcon);
if(fract > 0.4) fract= 0.4;
/*
compute the updated frequency, noting that in this
mode, y= ybar + x/waitx. using the usual exponential filter,
the update to the frequency is simply x/waitx scaled
by the time constant.
do not update the frequency if iflags == 4, since that
means we have a time step. the frequency estimate is
updated if we think this is a frequency step.
*/
if(autolock[0].flags != 4) autolock[0].ybar +=
fract*autolock[0].x/autolock[0].waitx;
autolock[0].sumerr= (1-fract)*autolock[0].sumerr +
fract*fabs(autolock[0].errx);
eval_elap -= autolock[0].waitx;
intvl=autolock[0].waitn; /*default delay*/
#ifdef KERNEL
/*
if kernel mods are supported and enabled, then our estimated
frequency offset can be transmitted directly to the kernel
where it will be done automatically. the units of the kernel
frequency variable are ps/s or e-12, whereas ybar is
dimensionless and thus has units of s/s.
*/
if(kernel == 1)
{
tmx.modes=MOD_LOCKCLOCK | MOD_FREQUENCY | MOD_HEALTH |
MOD_STATUS;
tmx.freq= -1e+12*autolock[0].ybar;
tmx.health=0;
tmx.status=TIME_OK;
/*
if the leap second status from the remote server is
different from our local notion of the leap second
flag, then do the right thing.
*/
if(lsr != lsl) /*if flags are different*/
{
lsl=lsr; /*set local to value of remote*/
if(lsl == 1) tmx.status |= STA_INS; /*insert*/
if(lsl == 2) tmx.status |= STA_DEL; /*drop*/
} /*end of flags are different*/
/*
remove a time error if it is significantly
larger than the uncertainty of the calibration
data.
the offset parameter is in units of ps. it is
defined as a long int, which means that it is
64 bits on an Alpha. It is not likely that any
reasonable time offset could cause a 64 bit time
difference to overflow -- even if it is in units of ps.
nevertheless, limit it just to be sure.
*/
if(fabs(autolock[0].x) > autolock[0].rms)
{
tmx.modes |= MOD_OFFSET;
if(autolock[0].x > 1e+7) autolock[0].x= 1e+7;
if(autolock[0].x < -1e+7) autolock[0].x= -1e+7;
tmx.offset= -1e+12*autolock[0].x; /*convert to ps*/
}
syscall(SYS_ntp_adjtime,&tmx);
} /*end of if kernel == 1*/
#endif /*KERNEL*/
#ifdef LINUX
/*
if LINUX is defined then we have to do the same jobs as
above, but we do them slightly differently.
First, if the leap second status from the remote server is
different from our local notion of the leap second
flag, then do the right thing.
then remove a time error as above. Note that the units
for time offset in LINUX are microseconds.
*/
tmx.status= TIME_OK;
tmx.modes=ADJ_STATUS;
if(lsr != lsl) /*if flags are different*/
{
lsl=lsr; /*set local to value of remote*/
if(lsl == 1) tmx.status |= STA_INS; /*insert*/
if(lsl == 2) tmx.status |= STA_DEL; /*drop*/
} /*end of flags are different*/
if(fabs(autolock[0].x) > autolock[0].rms)
{
tmx.modes |= ADJ_OFFSET_SINGLESHOT;
if(autolock[0].x > 100) tmx.offset=-1e+7;
else if(autolock[0].x < -100) tmx.offset= 1e+7;
else tmx.offset=-1e+6*autolock[0].x;
} /*end of time adjustment is significant*/
adjtimex(&tmx);
#endif /*LINUX*/
goto finfin; /*end of this round*/
}
finfin: /*write current states to file, ... */
if( (lop=fopen(SNAME,"w")) == NULL)
{
sprintf(errmsg,"Oops -- error opening state file %s.\n",SNAME);
putmsg(errmsg);
exit(-1);
}
putautolock(&lop,autolock); /*save current state*/
fclose(lop);
/*
if we are running in opmode = 1 then the purpose is
simply to evaluate the network and the clock. This is
done by running for a fixed time and then exiting.
stop the music when this time expires.
*/
if( (opmode == 1) && (ttot >= evalint) ) break;
/*
see if it is time to examine how well we have been
doing for the last day.
*/
if(eval_elap <= 0) /*yes, time for a retrospective look*/
{
/*
if the local clock is still free-running then
estimate the Allan variance using the y array.
the estimate is made for lags that are powers of 2.
and the operation ends when the adev subroutine
returns 0 meaning that the lag is now too big
for the number of frequency estimates currently
in the y array.
note that in the following, the first calculation
is done with lag = 1 and is stored in the avar
array at index 0.
if we are in mode 4 or later then the local clock
is being adjusted and the calculation doesn't mean
much. so skip it.
*/
if(autolock[0].mode >= 4) goto novar; /*skip adev if yes*/
nvar=0; /*initialize avar index*/
for(j=1; j < NUMSTATES; j *= 2) /*j sets the lag*/
{
if( (dtemp=adev(y,numby,j)) == 0) break;
sprintf(errmsg," Adev at lag %d is %le",
j*autolock[0].waitn,dtemp);
putmsg(errmsg);
avar[nvar++]= dtemp; /*save this value*/
} /*end of adev calculation*/
/*
the avar calculations are used to evaluate fcon
which is the effective time constant for averaging
y to produce ybar. the value of nvar gives the
number of adev calculations computed above.
look for the break-point in the variation of adev
with lag, but don't do anything if nvar <= 1, which
means we have fewer than two estimates.
we expect that avar will decrease at least as fast
as the square root of j before we get into the flicker
FM domain. look for the break in the slope that signals
that this.
if we can find this point see if it is roughly equal
to fcon, and adjust fcon if this is not true. this
is only a rough calculation, and don't change things
if fcon is right to within a factor of 2.
note that the j-th estimate is calculated using an
averaging time of 2^(j)*waitn, where the first estimate
uses j=0 -- see above.
if this routine changes the value then put a message
in the log file giving the new value.
*/
for(j=1; j<nvar; j++) /*loop over all adev calculations*/
{
if(avar[j-1]/avar[j] < 1.4) break; /*break is here*/
} /*end of loop over all adev calculations*/
if( (j > 1) &&
(j < nvar)) /*break found inside array*/
{
itemp= autolock[0].waitn << j; /*multiply by 2^j*/
if(autolock[0].fcon > 2*itemp) autolock[0].fcon=itemp;
else
if(autolock[0].fcon < itemp/4) autolock[0].fcon=itemp/2;
if(autolock[0].fcon != autolock[1].fcon)
{
sprintf(errmsg,
"value for fcon is now %d",autolock[0].fcon);
putmsg(errmsg);
} /*end of write out value if different*/
} /*end of break found inside array*/
novar: /*mode >= 4, don't compute Allan variance*/
/*
first question: is the algorithm providing an average
prediction error that is better, worse or about right
relative to the desired performance?
if not root 2 better and not root 2 worse then leave
the parameters alone -- they must be about right.
note that the units of sumerr are seconds whereas
sigma_desired is in milliseconds.
if the performance is more than root 2 better then
increase the time interval by a factor of 2 unless
we are already at the maximum value specified in the
parameter file, in which case don't increase past
that point.
note that it is quite likely that the prediction error
on the next cycle will be worse than we think because
we have just increased averaging time. Increase
the value of sumerr in this case as well. If the
interval is already at the maximum value then don't
increase sumerr.
*/
if(1414*autolock[0].sumerr < sigma_desired) /*root 2 better*/
{
numby= 0; /*reset y index for new waitn*/
autolock[0].waitn *= 2; /*increase interval between cycles*/
if(autolock[0].waitn > intvlmax)
autolock[0].waitn= intvlmax; /*not more than max*/
else
autolock[0].sumerr *= 1.4;
intvl= autolock[0].waitn; /*set new value for delay*/
} /*end of performance is better*/
else if(707*autolock[0].sumerr > sigma_desired) /*root 2 worse*/
{
numby= 0; /*reset y index for new waitn*/
autolock[0].waitn /= 2;/*decrease interval between cycles*/
if(autolock[0].waitn < intvlmin)
autolock[0].waitn= intvlmin; /*but not less than min*/
intvl= autolock[0].waitn; /*set new value for delay*/
} /*end of performance is worse*/
/*
second question:
is prediction error better, worse or about the same as
average measurement error on each cycle. if the two are
about the same then the process is dominated by measurement
noise and the clock noise is not too important. This means
that we might improve the algorithm by increasing the
number of measurements in each group.
tests:
1. if the measurement noise is much less than the prediction
error then we are dominated by clock noise and the extra
measurements are not worth the trouble. So decrease
the number of measurements in each group until we get
down to the minimum.
2. if the measurement noise is greater than the desired
performance, then the loop is not doing as well as
we specify. increase the number of calibrations in
each group until we get up to the maximum.
*/
if(4*autolock[0].rms_avg < autolock[0].sumerr)
{
ncal /= 2;
if(ncal < ncalmin) ncal= ncalmin;
}
else if(autolock[0].rms_avg > 2*sigma_desired)
{
ncal *= 2;
if(ncal > ncalmax) ncal= ncalmax;
}
eval_elap= EVALPERIOD; /*reset the counter for the next round*/
} /*end of retrospective look at performance*/
/*
display time of the next calibration cycle
if not in debug mode. note that this value will
be wrong if the calibration cycle ended in an error
since fday was not updated and still contains the
time of the last successful cycle.
*/
if( (argc > 1) && (debug == 0) )
{
cp=argv[1];
while(*cp != '\0') *(cp++) = ' ';
nmin=(autolock[0].fday/1000 + intvl)/60; /*convert to minutes*/
nhr=nmin/60;
nmin %= 60;
sprintf(argv[1],"-> %2d:%2.2d",nhr,nmin);
}
/*
if the kernel routines are present and enabled then set
a watch dog timer in the kernel for the number of seconds
that we are going to sleep. if that timer becomes positive
then the time has expired. if we don't reset it then we
have crashed. since our calculated frequency offset is
being removed automatically in this mode, we can just
go back to sleep without doing anything else.
*/
#ifdef KERNEL
if(kernel == 1)
{
tmx.modes= MOD_LOCKCLOCK | MOD_MAXERROR;
tmx.maxerror= -512*(intvl); /*reset time out counter*/
syscall(SYS_ntp_adjtime,&tmx); /*tell the kernel*/
sleep( (unsigned int) intvl); /*the sleep of the just*/
ttot += intvl; /* add this to the running time*/
goto awake; /* rise and shine! */
} /*end kernel == 1*/
#endif /*KERNEL*/
/*
if we get here then kernel mods are either not supported
on this system or they have been disabled.
if we are not in a lock mode, then we can just sleep the
uninterrupted sleep of the just until next time.
*/
if(autolock[0].mode < 5)
{
sleep( (unsigned int) intvl); /*and sleep until next time*/
ttot += intvl; /*add this sleep interval */
goto awake;
}
/*
if we get here then kernel mods are not supported and we
are in a lock mode. Instead of sleeping through the whole
time we have to schedule periodic corrections to the local
clock to keep it on time based on our estimate of ybar.
find out how long we must wait before a correction of 0.1 ms
will be needed. Then adjust this interval so that there will
be an integral number of them in the calibration delay and then
compute the exact correction needed each time.
if the offset frequency is very small, then the correction needed
in the full iwait seconds may be less than 1 ms. Compute the
correction needed in this case and apply it in two steps.
if the offset frequency is very large, then the delay between
0.1 ms corrections may be less than 1 sec. Since the maximum
slew rate is on the order of milliseconds/second, the best we can
do is to delay for 1 second and then adjust at the maximum
slew rate.
in all cases, fall through to awake when the adjustments are
finished.
*/
jwait= fabs(0.0001/autolock[0].ybar); /*time for 0.1 ms*/
if(jwait < 1) jwait= 1; /*not less than 1 second*/
if(jwait > intvl) jwait=intvl/2; /*if longer than whole cycle*/
jloop= intvl/jwait; /*number of adjustments in whole cycle*/
tcor= -(double)jwait*autolock[0].ybar;
for(i=0; i< jloop; i++) /*correction loop*/
{
adjt(tcor);
sleep(jwait);
}
awake: /*come here when we wake up after our sleep*/
/*
when we wake up, we have to push the autolock states down
by 1; the data acquired on the next cycle is then put in
autolock[0]. note that the values previously in the last
state will be lost and the values previously in the 0th
slot are duplicated into the first slot.
*/
pushautolock(autolock,0,NUMSTATES);
/*
slide y array down 1 slot if it is full
numby incremented after each transfer and thus points to
next free slot. Thus array is full when numby == NUMSTATES
test is on >= just in case we miss ==.
once we switch to any mode after 3 then numby doesn't
change anymore since there is no point evaluating the
frequency stability of the clock when it is being
adjusted.
*/
if(numby >= NUMSTATES) /*y array is full*/
{
for(i=1; i < NUMSTATES; i++) y[i-1]=y[i];
numby= NUMSTATES - 1; /*last element in array*/
} /*end of y array is full*/
/*
decrement filefull each time we push the autolock down,
and copy the file when filefull is decremented to
a small value. This will prevent data from getting
lost by being pushed off the bottom of the file.
*/
if(filefull-- < 20) /*file is almost full*/
{
#if ALPHA == 1
sprintf(filenam,"cp %s %s_%d",SNAME,SNAME,autolock[0].day);
#else
sprintf(filenam,"cp %s %s_%ld",SNAME,SNAME,autolock[0].day);
#endif
system(filenam);
pid=wait(status); /*wait for copy to finish*/
filefull = NUMSTATES; /*reset for next round */
} /*end of file is almost full*/
} /* end of the main infinite while loop*/
exit(0); /* can get here only on a break out of while loop*/
}