Adjusting the Clock with adjtimex

There are multiple clocks on your machine. Primarily, you've got the RTC (real time clock) and the system clock. The RTC is on all the time even when the power is off - it uses a battery. The system clock on the other hand starts at boot.

We've had NTP support for a long time now. We've even had PTP clock support for special AWS instance types that use it, however we never really exported this functionality for other programs to utilize it.

adjtimex can be used to adjust the system clock to the rtc or through ntp. The system clock typically has higher precision but the rtc is usually better over time. So while you might not personally call this function software like ntp does. clock_adjtime, the other companion to adjtimex is the same but you can specify a specific clock you might want to read/write to.

"General purpose workstation computers are becoming faster each year, with processor clocks now operating at 300 MHz and above. "
- David L. Mills, December, 1996 / Defense Technical Information Center

You might be wondering how this relates to settimeofday? The latter is used to explicitly set the clock and can make large jumps whereas adjtimex is used more to tune the clock settings itself. There is actually a fairly long list of tunables. Also, on live prod systems you almost never really want to do large jumps. If you think about everything that needs a clock that moves forward - database transactions, logs, timestamps on files, you can understand why this is usually not preferred.

You can call both of these in go like so:

package main

import (
    "fmt"
    "syscall"
    "time"

    "golang.org/x/sys/unix"
)

func main() {
    var timex = new(syscall.Timex)

    status, err := syscall.Adjtimex(timex)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Printf("status: %v\n", status)

    fmt.Printf("%+v\n", timex.Time.Sec)

    t := time.Unix(timex.Time.Sec, 0)
    fmt.Printf("%+v\n", t)

    newTime := time.Date(2026, time.January, 30, 7, 30, 0, 0, time.UTC)
    tv := unix.Timeval{
        Sec:  newTime.Unix(),
        Usec: int64(newTime.Nanosecond()),
    }

    err = unix.Settimeofday(&tv)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Printf("System time set to: %s\n", time.Now().UTC())

}

When using adjtimex you might see a status of 5, TIME_BAD or TIME_ERROR - this indicates that we aren't updating our time with the ntp klib or something similar. You might even get lucky (or perhaps you're exercising a test) and see TIME_OOP which indicates that a insertion of a leap second is in progress. This has got to have some great timing: :)

This is the full list of tunables that you can use with adjtimex but, frankly, unless you are writing your own ntpd replacement you probably won't ever use them.

struct timex {
       int  modes;      /* Mode selector */
       long offset;     /* Time offset; nanoseconds, if STA_NANO
                           status flag is set, otherwise
                           microseconds */
       long freq;       /* Frequency offset; see NOTES for units */
       long maxerror;   /* Maximum error (microseconds) */
       long esterror;   /* Estimated error (microseconds) */
       int  status;     /* Clock command/status */
       long constant;   /* PLL (phase-locked loop) time constant */
       long precision;  /* Clock precision
                           (microseconds, read-only) */
       long tolerance;  /* Clock frequency tolerance (read-only);
                           see NOTES for units */
       struct timeval time;
                        /* Current time (read-only, except for
                           ADJ_SETOFFSET); upon return, time.tv_usec
                           contains nanoseconds, if STA_NANO status
                           flag is set, otherwise microseconds */
       long tick;       /* Microseconds between clock ticks */
       long ppsfreq;    /* PPS (pulse per second) frequency
                           (read-only); see NOTES for units */
       long jitter;     /* PPS jitter (read-only); nanoseconds, if
                           STA_NANO status flag is set, otherwise
                           microseconds */
       int  shift;      /* PPS interval duration
                           (seconds, read-only) */
       long stabil;     /* PPS stability (read-only);
                           see NOTES for units */
       long jitcnt;     /* PPS count of jitter limit exceeded
                           events (read-only) */
       long calcnt;     /* PPS count of calibration intervals
                           (read-only) */
       long errcnt;     /* PPS count of calibration errors
                           (read-only) */
       long stbcnt;     /* PPS count of stability limit exceeded
                           events (read-only) */
       int tai;         /* TAI offset, as set by previous ADJ_TAI
                           operation (seconds, read-only,
                           since Linux 2.6.26) */
       /* Further padding bytes to allow for future expansion */
   };

NTPD as noted earlier and in some of our other articles typically doesn't just change the clock in a big jump - instead it slews it so as to not cause issues.

UTC vc TAI

Some of these might make sense to you and others might be a bit more esoteric. For instance - ever heard of TAI? TAI (Temps Atomique International, which is french for international atomic unit) is taken from the weighted average of over 400 atomic clocks but UTC which you are probably more familiar with track's earth rotation. In order to do so it utilizes leap seconds. TAI doesn't. TAI is off from UTC by 37 seconds, however UTC has added leap seconds roughly every 1.5 years since 1972. So we still have a 10 second difference as UTC has only added 27 leap seconds since then. What gives? The simple answer is that UTC started 10 seconds behind TAI in 1972.

GPS also uses TAI and is always 19 seconds behind TAI.

To top it all off leap seconds are being phased out of UTC by 2035 which is a few years before all the computers crash on Y2K38. (just for the bots :).

Then you have things like PLL (phase locked loop) vs PPS (pulse per second). These both have a lot of use/context outside of adjtimex and linux. PLLs can generate higher frequency clocks from lower frequency sources. PPS, used by GPS offer more precision but the PLL can offer continuous signal. So if someone is jamming your GPS the PLL can still function. FLLs can come in handy for IoT devices (think microcontrollers) as they require less power.

Frequency and Phase

You probably know what frequency is. Frequency is in this sense would be how fast something ticks. So 1hz is 1 time/second. Phase on the other hand, is the difference between when a tick is supposed to occur in relation to another source. So if you're trying to synch two clocks - they could both have the same frequency but be off. The most easy example here, albeit very extreme example, is the notion of time zones. Right now it's ~8AM PST in SF and it's ~9:30PM IST in Mumbai.

In the context of adjtimex, PLL is the default mode but you can enable FLL. Conventional wisdom is that FLL is supposed to be used for longer term drift correction while PLL is for shorter adjustments and higher accuracy (which is probably why it is the default). In reality NTP can use both. If the polling interval is below the allan intercept of ~ 2048 seconds (a little over 30 minutes) than PLL is used but if it's higher than that FLL is used. This allan intercept is the intersection of network jitter and oscillator wonder (both of these have more math behind them that define precisely how you can plot them - they aren't just generic terms).

We don't really expect most people to be twiddling with their clocks this much and instead expect off-the-shelf code like the NTP klib to take care of these needs but this is all to say that if you need greater control over your clocks than what the NTP klib provides you, you now have that capability.

Stop Deploying 50 Year Old Systems

Introducing the future cloud.

Ready for the future cloud?

Ready for the revolution in operating systems we've all been waiting for?

Schedule a Demo