Re: Detecting shift of CLOCK_REALTIME with clock_nanosleep (again)

Richard Cochran
2013-01-21T19:12:41Z

I still think that your problems can be solved at the application
level quite easily. Just out of curiosity, I wrote a little test
program, below, and I think it shows a promising direction to solve
your issue. (The 'clk_cmp' function was taken from the userland servo
in the phc2sys program, from the linuxptp project.)

I am not sure what your application wants to do if it should notice a
jump in CLOCK_REALTIME: keep the old phase angle or re-align to the
new UTC second. My test program does not shift its phase, but I think
you would know how to do it.

Running on my dinky atom netbook (32 bit, no vdso, no RT) in an
unscientific test, I see the value of 'expected - offset' typically
around a few hundred nanoseconds, with occasional outliers of a few
microseconds. With RT-PREMPT these could surely be avoided, or by
using a moving average of say, 10 readings.

Also, rather than jumping to the new phase angle (if that is what you
want in fact to do), you could smoothly ajust the loop phase using a
PI controller or similar.

Anyhow, here it is. What do you think?

Thanks,
Richard

---
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>

#define NS_PER_SEC 1000000000LL

static int clk_cmp(clockid_t clkid, clockid_t sysclk, int readings,
int64_t *offset, uint64_t *ts)
{
struct timespec tdst1, tdst2, tsrc;
int i;
int64_t interval, best_interval = INT64_MAX;

/* Pick the quickest clkid reading. */
for (i = 0; i < readings; i++) {
if (clock_gettime(sysclk, &tdst1) ||
clock_gettime(clkid, &tsrc) ||
clock_gettime(sysclk, &tdst2)) {
perror("clock_gettime");
return -1;
}

interval = (tdst2.tv_sec - tdst1.tv_sec) * NS_PER_SEC +
tdst2.tv_nsec - tdst1.tv_nsec;

if (best_interval > interval) {
best_interval = interval;
*offset = (tdst1.tv_sec - tsrc.tv_sec) * NS_PER_SEC +
tdst1.tv_nsec - tsrc.tv_nsec + interval / 2;
*ts = tdst2.tv_sec * NS_PER_SEC + tdst2.tv_nsec;
}
}

return 0;
}

int main(int argc, char *argv[])
{
int64_t expected, offset;
uint64_t systs;
struct timespec next;
struct timespec incr = {
0, 250000000
};
clockid_t clk = CLOCK_MONOTONIC;
int n_readings = 1;

/*
* Calibrate the offset.
*/
if (clk_cmp(clk, CLOCK_REALTIME, 25, &expected, &systs)) {
return -1;
}
if (clock_gettime(clk, &next)) {
perror("clock_gettime");
return -1;
}

next.tv_sec++;

while (1) {
if (clock_nanosleep(clk, TIMER_ABSTIME, &next, NULL)) {
perror("clock_nanosleep");
break;
}
if (clk_cmp(clk, CLOCK_REALTIME, n_readings, &offset, &systs)) {
break;
}
printf("%" PRId64 "\n", expected - offset);
/*
* At this point, if the difference is more than, say,
* 10 microseconds, then you could simply recalibrate
* the offset and set the next deadline.
*/
next.tv_sec += incr.tv_sec;
next.tv_nsec += incr.tv_nsec;
while (next.tv_nsec >= NS_PER_SEC) {
next.tv_nsec -= NS_PER_SEC;
next.tv_sec++;
}
}

return 0;
}