[LEAPSECS] JD & MJD, UT1 & UTC

Martin Burnicki martin.burnicki at burnicki.net
Wed Jan 4 07:40:09 EST 2017


Zefram wrote:
> Martin Burnicki wrote:
>> Yes, but the advantage of a nonnormalized tv_nsec field is that it can
>> be used with "legacy" structures
> 
> A non-normalised tm_sec has that advantage too.

Sorry, maybe I was not clear enough.

In a struct timespec you can use a nonnormalized tv_nsec field to
indicate a leap second, and in a struct tm you can set the tm_sec field
to 60.

The limitation I see is that a usual conversion function like gmtime()
just takes the time_t part (tv_sec) but not the fractions, so gmtime()
is unable to see that tv_nsec is > 10000000, and as a result set tm_sec
to 60. So you'd always have to use a wrapper function which accepts a
struct timespec, then calls gmtime() to determine the basic date and
time, and finally increment tm_sec and subtract 1000000000 from tv_nsec
to yield numbers that can be displayed properly.

>> On the other hand, this may break existing applications which aren't
>> aware that the number in tv_nsec can exceed 1000000000, so those might
>> display strange timestamps. ;-)
> 
> Funny you should say that.  Just yesterday I noticed a problem with the
> adjtimex(8) tool on my Debian system:
> 
> $ /sbin/adjtimex -p
>          mode: 0
>        offset: -436678
>     frequency: 5752562
>      maxerror: 214936
>      esterror: 4374
>        status: 8193
> time_constant: 10
>     precision: 1
>     tolerance: 32768000
>          tick: 9999
>      raw time:  1483529963s 29863426us = 1483529963.29863426
> 
> Observe that the "raw time" field shows a fractional part in the tens
> of millions of microseconds, amounting to nearly thirty seconds into
> this second.  That's a highly non-normalised struct timeval.  Where the
> tool tries to display the structure as a single scalar, the output is
> inconsistent with the structure, treating the `microsecond' unit as if it
> were instead 1e-8 s.  That part of the output arises from using a simple
> "%d.%06d" printf format, with the latter field being overflowed by the
> out-of-range microseconds value.
> 
> What's actually going on is that the tv_usec field isn't being used to
> store a count of microseconds, but a count of *nano*seconds.  There's a
> status flag STA_NANO, which is the 8192 part of the 8193 `status'
> value, which indicates to sufficiently aware users of adjtimex(2) that
> the time.tv_usec and offset fields are in nanoseconds rather than the
> usual microseconds.  I've previously seen this flag on FreeBSD, so *my*
> adjtimex(2)-using code handles it, but it was news to me that Linux has
> adopted it.  Also news to the author of adjtimex(8), apparently.

It was also my initial thought when I saw the output you listed above
that the fractions might be nanoseconds rather than microseconds. ;-)

In the ntpd source code I see a number of places like this:

#ifdef STA_NANO
  clock_offset = ntv.offset / 1e9;
#else /* STA_NANO */
  clock_offset = ntv.offset / 1e6;
#endif /* STA_NANO */

So this is interpreted here as nanoseconds or microseconds depending on
whether STA_NANO is defined *at compile time*.

IMO this is not an optimal solution. I've seen cases under Linux where
the timex.h file shipped with the kernel source
(/usr/src/linux/include/linux/timex.h) and used to compile the kernel
defined STA_NANO, while an older copy of timex.h shipped with glibc
(/usr/include/linux/timex.h) did *not* include it. Since the latter is
used by default when compiling a user space application like ntpd this
resulted in an ntpd binary which assumed the adjtimex() returns
microseconds, while in fact the kernel deals with nanoseconds.

So a better solution would be if the the kernel sets the STA_NANO bit if
it provides nanoseconds, and applications check at runtime if the bit is
set in the status returned by adjtimex().

> I have to say, this is a bad way to futz with resolution of the system
> call.  Unaware callers just go wrong, as seen above.  There's nothing
> that signals that the flag bit can't just be ignored if not understood.
> The resolution should have been specified by the caller, in the
> mode-selection field.  Yes, it would mean kernel code to translate
> between resolutions, but the kernel already has backcompat code for
> many syscalls.  This would be milder than a lot of the backcompat code
> that's already there.

I haven't looked carefully at the specs and implementation right now, so
I'm not sure if the kernel decides at startup, if it sets the STA_NANO
bit itself if compiled with nanosecond support, or if this flag has to
be passed to the kernel via the adjtimex() call first to indicate if the
caller wants fractions in microseconds or nanoseconds.

Anyway, I think the reason is historically, when the structure was
introduced then only microsecond resolution was supported, and later a
somewhat compatible way was introduced to support nanoseconds.

Martin



More information about the LEAPSECS mailing list