📄 timesync.c
字号:
#include <u.h>#include <libc.h>#include <auth.h>#include <ip.h>#include <mp.h>/* nanosecond times */#define SEC 1000000000LL#define MIN (60LL*SEC)#define HOUR (60LL*MIN)#define DAY (24LL*HOUR)enum { Fs, Rtc, Ntp, Utc, Gps, HZAvgSecs= 3*60, /* target averaging period for the frequency in seconds */ MinSampleSecs= 60, /* minimum sampling time in seconds */};char *dir = "/tmp"; // directory sample files live inchar *logfile = "timesync";char *timeserver;char *Rootid;int utcfil;int gpsfil;int debug;int impotent;int logging;int type;int gmtdelta; // rtc+gmtdelta = gmtuvlong avgerr;// ntp server infoint stratum = 14;vlong mydisp, rootdisp;vlong mydelay, rootdelay;vlong avgdelay;vlong lastutc;uchar rootid[4];char *sysid;int myprec;// list of time samplestypedef struct Sample Sample;struct Sample{ Sample *next; uvlong ticks; vlong ltime; vlong stime;};// ntp packettypedef struct NTPpkt NTPpkt;struct NTPpkt{ uchar mode; uchar stratum; uchar poll; uchar precision; uchar rootdelay[4]; uchar rootdisp[4]; uchar rootid[4]; uchar refts[8]; uchar origts[8]; // departed client uchar recvts[8]; // arrived at server uchar xmitts[8]; // departed server uchar keyid[4]; uchar digest[16];};// ntp servertypedef struct NTPserver NTPserver;struct NTPserver{ NTPserver *next; char *name; uchar stratum; uchar precision; vlong rootdelay; vlong rootdisp; vlong rtt; vlong dt;};NTPserver *ntpservers;enum{ NTPSIZE= 48, // basic ntp packet NTPDIGESTSIZE= 20, // key and digest};// error bound of last sampleulong ε;static void addntpserver(char *name);static int adjustperiod(vlong diff, vlong accuracy, int secs);static void background(void);static int caperror(vlong dhz, int tsecs, vlong taccuracy);static long fstime(void);static int gettime(vlong *nsec, uvlong *ticks, uvlong *hz); // returns time, ticks, hzstatic int getclockprecision(vlong);static vlong gpssample(void);static void hnputts(void *p, vlong nsec);static void hnputts(void *p, vlong nsec);static void inittime(void);static vlong nhgetts(void *p);static vlong nhgetts(void *p);static void ntpserver(char*);static vlong ntpsample(void);static int ntptimediff(NTPserver *ns);static int openfreqfile(void);static vlong readfreqfile(int fd, vlong ohz, vlong minhz, vlong maxhz);static long rtctime(void);static vlong sample(long (*get)(void));static void setpriority(void);static void setrootid(char *d);static void settime(vlong now, uvlong hz, vlong delta, int n); // set time, hz, delta, periodstatic vlong utcsample(void);static uvlong vabs(vlong);static uvlong whatisthefrequencykenneth(uvlong hz, uvlong minhz, uvlong maxhz, vlong dt, vlong ticks, vlong period);static void writefreqfile(int fd, vlong hz, int secs, vlong diff);// ((1970-1900)*365 + 17/*leap days*/)*24*60*60#define EPOCHDIFF 2208988800ULvoidmain(int argc, char **argv){ int i; int secs; // sampling period int tsecs; // temporary sampling period int t, fd; Sample *s, *x, *first, **l; vlong diff, accuracy, taccuracy; uvlong hz, minhz, maxhz, period, nhz; char *servenet[4]; int nservenet; char *a; Tm tl, tg; type = Fs; // by default, sync with the file system debug = 0; accuracy = 1000000LL; // default accuracy is 1 millisecond nservenet = 0; tsecs = secs = MinSampleSecs; timeserver = ""; ARGBEGIN{ case 'a': a = ARGF(); if(a == nil) sysfatal("bad accuracy specified"); accuracy = strtoll(a, 0, 0); // accuracy specified in ns if(accuracy <= 1LL) sysfatal("bad accuracy specified"); break; case 'f': type = Fs; stratum = 2; break; case 'r': type = Rtc; stratum = 0; break; case 'U': type = Utc; stratum = 1; break; case 'G': type = Gps; stratum = 1; break; case 'n': type = Ntp; break; case 'D': debug = 1; break; case 'd': dir = ARGF(); break; case 'L': // // Assume time source in local time rather than GMT. // Calculate difference so that rtctime can return GMT. // This is useful with the rtc on PC's that run Windows // since Windows keeps the local time in the rtc. // t = time(0); tl = *localtime(t); tg = *gmtime(t); // if the years are different, we're at most a day off, so just rewrite if(tl.year < tg.year){ tg.year--; tg.yday = tl.yday + 1; }else if(tl.year > tg.year){ tl.year--; tl.yday = tg.yday+1; } assert(tl.year == tg.year); tg.sec -= tl.sec; tg.min -= tl.min; tg.hour -= tl.hour; tg.yday -= tl.yday; gmtdelta = tg.sec+60*(tg.min+60*(tg.hour+tg.yday*24)); assert(abs(gmtdelta) <= 24*60*60); break; case 'i': impotent = 1; break; case 'I': Rootid = ARGF(); break; case 's': if(nservenet >= nelem(servenet)) sysfatal("too many networks to serve on"); a = ARGF(); if(a == nil) sysfatal("must specify network to serve on"); servenet[nservenet++] = a; break; case 'l': logging = 1; break; case 'S': a = ARGF(); if(a == nil) sysfatal("bad stratum specified"); stratum = strtoll(a, 0, 0); break; }ARGEND; fmtinstall('E', eipfmt); fmtinstall('I', eipfmt); fmtinstall('V', eipfmt); sysid = getenv("sysname"); // detach from the current namespace if(debug) rfork(RFNAMEG); switch(type){ case Utc: if(argc > 0) timeserver = argv[0]; else sysfatal("bad time source"); break; case Gps: if(argc > 0) timeserver = argv[0]; else timeserver = "/mnt/gps/time"; break; case Fs: if(argc > 0) timeserver = argv[0]; else timeserver = "/srv/boot"; break; case Ntp: if(argc > 0){ for(i = 0; i <argc; i++) addntpserver(argv[i]); } else { addntpserver("$ntp"); } break; } setpriority(); // figure out our time interface and initial frequency inittime(); gettime(0, 0, &hz); minhz = hz/10; maxhz = hz*10; myprec = getclockprecision(hz); // convert the accuracy from nanoseconds to ticks taccuracy = hz*accuracy/SEC; // // bind in clocks // switch(type){ case Fs: fd = open(timeserver, ORDWR); if(fd < 0) sysfatal("opening %s: %r\n", timeserver); if(amount(fd, "/n/boot", MREPL, "") < 0) sysfatal("mounting %s: %r\n", timeserver); close(fd); break; case Rtc: bind("#r", "/dev", MAFTER); if(access("/dev/rtc", AREAD) < 0) sysfatal("accessing /dev/rtc: %r\n"); break; case Utc: fd = open(timeserver, OREAD); if(fd < 0) sysfatal("opening %s: %r\n", timeserver); utcfil = fd; break; case Gps: fd = open(timeserver, OREAD); if(fd < 0) sysfatal("opening %s: %r\n", timeserver); gpsfil = fd; break; } // // start a local ntp server(s) // for(i = 0; i < nservenet; i++){ switch(rfork(RFPROC|RFFDG|RFMEM|RFNOWAIT)){ case -1: sysfatal("forking: %r"); break; case 0: ntpserver(servenet[i]); _exits(0); break; default: break; } } // get the last known frequency from the file fd = openfreqfile(); hz = readfreqfile(fd, hz, minhz, maxhz); // this is the main loop. it gets a sample, adjusts the // clock and computes a sleep period until the next loop. // we balance frequency drift against the length of the // period to avoid blowing the accuracy limit. first = nil; l = &first; avgerr = accuracy>>1; for(;; background(),sleep(tsecs*(1000))){ s = mallocz(sizeof(*s), 1); diff = 0; // get times for this sample ε = ~0; switch(type){ case Fs: s->stime = sample(fstime); break; case Rtc: s->stime = sample(rtctime); break; case Utc: s->stime = utcsample(); if(s->stime == 0LL){ if(logging) syslog(0, logfile, "no sample"); free(s); if (secs > 60 * 15) tsecs = 60*15; continue; } break; case Ntp: diff = ntpsample(); if(diff == 0LL){ if(logging) syslog(0, logfile, "no sample"); free(s); if(secs > 60*15) tsecs = 60*15; continue; } break; case Gps: diff = gpssample(); if(diff == 0LL){ if(logging) syslog(0, logfile, "no sample"); free(s); if(secs > 60*15) tsecs = 60*15; continue; } } // use fastest method to read local clock and ticks gettime(&s->ltime, &s->ticks, 0); if(type == Ntp || type == Gps) s->stime = s->ltime + diff; // if the sample was bad, ignore it if(s->stime < 0){ free(s); continue; } // reset local time diff = s->stime - s->ltime; if(diff > 10*SEC || diff < -10*SEC){ // we're way off, just set the time secs = MinSampleSecs; settime(s->stime, 0, 0, 0); } else { // keep a running average of the error. avgerr = (avgerr>>1) + (vabs(diff)>>1); // the time to next sample depends on how good or // bad we're doing. tsecs = secs = adjustperiod(diff, accuracy, secs); // work off the fixed difference. This is done // by adding a ramp to the clock. Each 100th of a // second (or so) the kernel will add diff/(4*secs*100) // to the clock. we only do 1/4 of the difference per // period to dampen any measurement noise. // // any difference greater than ε we work off during the // sampling period. if(abs(diff) > ε){ if(diff > 0) settime(-1, 0, diff-((3*ε)/4), secs); else settime(-1, 0, diff+((3*ε)/4), secs); } else settime(-1, 0, diff, 4*secs); } if(debug) fprint(2, "δ %lld avgδ %lld f %lld\n", diff, avgerr, hz); // dump old samples (keep at least one) while(first != nil){ if(first->next == nil) break; if(s->stime - first->next->stime < DAY) break; x = first; first = first->next; free(x); } // The sampling error is limited by the total error. If // we make sure the sampling period is at least 16 million // times the average error, we should calculate a frequency // with on average a 1e-7 error. // // So that big hz changes don't blow our accuracy requirement, // we shorten the period to make sure that δhz*secs will be // greater than the accuracy limit. period = avgerr<<24; for(x = first; x != nil; x = x->next){ if(s->stime - x->stime < period) break; if(x->next == nil || s->stime - x->next->stime < period) break; } if(x != nil){ nhz = whatisthefrequencykenneth( hz, minhz, maxhz, s->stime - x->stime, s->ticks - x->ticks, period); tsecs = caperror(vabs(nhz-hz), tsecs, taccuracy); hz = nhz; writefreqfile(fd, hz, (s->stime - x->stime)/SEC, diff); } // add current sample to list. *l = s; l = &s->next; if(logging) syslog(0, logfile, "δ %lld avgδ %lld hz %lld", diff, avgerr, hz); }}//// adjust the sampling period with some histeresis//static intadjustperiod(vlong diff, vlong accuracy, int secs){ uvlong absdiff; absdiff = vabs(diff); if(absdiff < (accuracy>>1)) secs += 60; else if(absdiff > accuracy) secs >>= 1; else secs -= 60; if(secs < MinSampleSecs) secs = MinSampleSecs; return secs;}//// adjust the frequency//static uvlongwhatisthefrequencykenneth(uvlong hz, uvlong minhz, uvlong maxhz, vlong dt, vlong ticks, vlong period){ static mpint *mpdt; static mpint *mpticks; static mpint *mphz; static mpint *mpbillion; uvlong ohz = hz; // sanity check if(dt <= 0 || ticks <= 0) return hz; if(mphz == nil){ mphz = mpnew(0); mpbillion = uvtomp(SEC, nil); } // hz = (ticks*SEC)/dt mpdt = vtomp(dt, mpdt); mpticks = vtomp(ticks, mpticks); mpmul(mpticks, mpbillion, mpticks); mpdiv(mpticks, mpdt, mphz, nil); hz = mptoui(mphz); // sanity if(hz < minhz || hz > maxhz) return ohz; // damp the change if we're shorter than the target period if(period > dt) hz = (12ULL*ohz + 4ULL*hz)/16ULL; settime(-1, hz, 0, 0); return hz;}// We may be changing the frequency to match a bad measurement// or to match a condition no longer in effet. To make sure// that this doesn't blow our error budget over the next measurement// period, shorten the period to make sure that δhz*secs will be// less than the accuracy limit. Here taccuracy is accuracy converted// from nanoseconds to ticks.static intcaperror(vlong dhz, int tsecs, vlong taccuracy){ if(dhz*tsecs <= taccuracy) return tsecs; if(debug) fprint(2, "δhz %lld tsecs %d tacc %lld\n", dhz, tsecs, taccuracy); tsecs = taccuracy/dhz; if(tsecs < MinSampleSecs) tsecs = MinSampleSecs; return tsecs;}//// kernel interface//enum{ Ibintime, Insec, Itiming,};int ifc;int bintimefd = -1;int timingfd = -1;int nsecfd = -1;int fastclockfd = -1;static voidinittime(void){ int mode; if(impotent) mode = OREAD; else mode = ORDWR; // bind in clocks if(access("/dev/time", 0) < 0) bind("#c", "/dev", MAFTER); if(access("/dev/rtc", 0) < 0) bind("#r", "/dev", MAFTER); // figure out what interface we have ifc = Ibintime; bintimefd = open("/dev/bintime", mode); if(bintimefd >= 0) return; ifc = Insec; nsecfd = open("/dev/nsec", mode); if(nsecfd < 0) sysfatal("opening /dev/nsec"); fastclockfd = open("/dev/fastclock", mode); if(fastclockfd < 0) sysfatal("opening /dev/fastclock"); timingfd = open("/dev/timing", OREAD); if(timingfd < 0) return; ifc = Itiming;}//// convert binary numbers from/to kernel//static uvlong uvorder = 0x0001020304050607ULL;static uchar*be2vlong(vlong *to, uchar *f){ uchar *t, *o; int i; t = (uchar*)to; o = (uchar*)&uvorder; for(i = 0; i < sizeof(vlong); i++) t[o[i]] = f[i]; return f+sizeof(vlong);}static uchar*vlong2be(uchar *t, vlong from){ uchar *f, *o; int i; f = (uchar*)&from; o = (uchar*)&uvorder; for(i = 0; i < sizeof(vlong); i++) t[i] = f[o[i]]; return t+sizeof(vlong);}static long order = 0x00010203;static uchar*be2long(long *to, uchar *f){ uchar *t, *o; int i; t = (uchar*)to; o = (uchar*)ℴ for(i = 0; i < sizeof(long); i++) t[o[i]] = f[i]; return f+sizeof(long);}static uchar*long2be(uchar *t, long from){ uchar *f, *o; int i; f = (uchar*)&from; o = (uchar*)ℴ for(i = 0; i < sizeof(long); i++) t[i] = f[o[i]]; return t+sizeof(long);}//// read ticks and local time in nanoseconds//static intgettime(vlong *nsec, uvlong *ticks, uvlong *hz){ int i, n;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -