⏱️ Timer Scheduling & Accuracy

⏱️ Timer Scheduling & Accuracy
Chapter 23 | Part 2 of 9 — How Precise Are Linux Timers?
🕐 Jiffy
Kernel Tick
🎯 Precision
Up to µs
🔧 Config
HIGH_RES

Why Aren’t Timers Always Exact?

When you set a timer for exactly 1 second, it might fire at 1.004s. This is not a bug — it is how OS scheduling works. The kernel only gets to check timers at each “tick” of its internal clock.

Understanding timer accuracy is essential for writing reliable time-sensitive programs.

🔬 The Kernel Jiffy — The Root of Timer Inaccuracy

Linux uses a software clock that ticks at a fixed rate called HZ. Each tick is a jiffy.

HZ Value Jiffy Duration Typical System Timer Rounding
100 10 ms Older kernels Rounded to 10ms multiples
250 4 ms Default Linux desktop Rounded to 4ms multiples
1000 1 ms RT / low-latency Rounded to 1ms multiples
HIGH_RES ~1 µs Kernel ≥ 2.6.21 with CONFIG Hardware-limited precision

📊 Timer Rounding in Action

If jiffy = 4ms and you request a 19ms timer, it rounds UP to 20ms (the next jiffy boundary).

Requested: 19ms
→ round up →
Actual: 20ms (5 jiffies)
with jiffy = 4ms
0ms
4ms
8ms
12ms
16ms
20ms ✓ FIRE
24ms
Key insight: Interval timers do NOT creep — if a 2s timer is 4ms late once, the NEXT expiry is still at exactly 2s after the previous, not 2.004s. The schedule stays anchored.

⚠️ Sources of Timer Delivery Delay
1. Jiffy Rounding
Timer value rounded up to next jiffy boundary. Fixed at setup time.
2. CPU Scheduling Delay
Process may not get CPU immediately after signal generation — other tasks may be running.
3. Signal Blocking
If signal is masked via sigprocmask(), it is queued and delivered later when unblocked.

🚀 High-Resolution Timers (kernel ≥ 2.6.21)

With CONFIG_HIGH_RES_TIMERS enabled in the kernel, timer resolution is no longer limited by the jiffy. Hardware timers (HPET, TSC) are used directly.

Without HIGH_RES

  • Resolution = jiffy size (1–10ms)
  • setitimer / nanosleep limited
  • Fine for most apps
With HIGH_RES

  • Resolution ~1 microsecond
  • Hardware-limited accuracy
  • Check via clock_getres()

💻 Example 1: Check Clock Resolution

Use clock_getres() to discover whether high-res timers are available.

#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <time.h>

int main(void) {
    struct timespec res;

    /* Check resolution of CLOCK_REALTIME */
    clock_getres(CLOCK_REALTIME, &res);
    printf("CLOCK_REALTIME resolution: %ld ns\n",
           res.tv_sec * 1000000000L + res.tv_nsec);

    /* Check resolution of CLOCK_MONOTONIC */
    clock_getres(CLOCK_MONOTONIC, &res);
    printf("CLOCK_MONOTONIC resolution: %ld ns\n",
           res.tv_sec * 1000000000L + res.tv_nsec);

    /* If resolution is 1ns, high-res timers are active.
       If 1ms or more, standard jiffy-based timers are used. */
    if (res.tv_nsec == 1 && res.tv_sec == 0)
        printf("High-resolution timers ENABLED\n");
    else
        printf("Standard jiffy-based timers (resolution: %ld ms)\n",
               (res.tv_sec * 1000L) + (res.tv_nsec / 1000000L));

    return 0;
}

/* Compile: gcc -o check_res check_res.c -lrt
   Sample output (high-res):
     CLOCK_REALTIME resolution: 1 ns
     CLOCK_MONOTONIC resolution: 1 ns
     High-resolution timers ENABLED
*/

💻 Example 2: Measure Real Timer Accuracy

Measure the actual delay between requested and delivered timer expiry.

#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

static struct timespec fired_at;
static volatile int got_alarm = 0;

void handler(int sig) {
    /* Record time of signal delivery */
    clock_gettime(CLOCK_MONOTONIC, &fired_at);
    got_alarm = 1;
}

int main(void) {
    struct sigaction sa;
    struct itimerval itv;
    struct timespec before, after;
    long requested_us = 50000; /* 50 ms = 50000 us */
    long actual_us;

    sa.sa_handler = sigalrm_handler;
    sa.sa_flags   = 0;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = handler;
    sigaction(SIGALRM, &sa, NULL);

    /* Request 50ms one-shot timer */
    itv.it_value.tv_sec    = 0;
    itv.it_value.tv_usec   = requested_us;
    itv.it_interval.tv_sec  = 0;
    itv.it_interval.tv_usec = 0;

    clock_gettime(CLOCK_MONOTONIC, &before);
    setitimer(ITIMER_REAL, &itv, NULL);

    while (!got_alarm)
        pause();

    actual_us = (fired_at.tv_sec - before.tv_sec) * 1000000L
              + (fired_at.tv_nsec - before.tv_nsec) / 1000L;

    printf("Requested: %ld µs\n", requested_us);
    printf("Actual   : %ld µs\n", actual_us);
    printf("Overrun  : %ld µs\n", actual_us - requested_us);

    return 0;
}

/* Example output (4ms jiffy system):
   Requested: 50000 µs
   Actual   : 52000 µs
   Overrun  :  2000 µs  (rounded up to next 4ms boundary)
*/

💻 Example 3: Periodic Timer — Non-Creeping Proof

Show that periodic timer intervals stay anchored even if individual deliveries are late.

#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>

static struct timespec start_ts;
static int count = 0;

void handler(int sig) {
    struct timespec now;
    long elapsed_ms;
    count++;
    clock_gettime(CLOCK_MONOTONIC, &now);
    elapsed_ms = (now.tv_sec  - start_ts.tv_sec)  * 1000L
               + (now.tv_nsec - start_ts.tv_nsec) / 1000000L;
    printf("Expiry #%d at %ld ms (expected ~%d ms)\n",
           count, elapsed_ms, count * 100);
    if (count >= 5) {
        struct itimerval stop = {{0,0},{0,0}};
        setitimer(ITIMER_REAL, &stop, NULL);
    }
}

int main(void) {
    struct sigaction sa;
    struct itimerval itv;

    sa.sa_handler = handler;
    sa.sa_flags   = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGALRM, &sa, NULL);

    /* 100ms periodic timer */
    itv.it_value.tv_sec    = 0;
    itv.it_value.tv_usec   = 100000;
    itv.it_interval.tv_sec  = 0;
    itv.it_interval.tv_usec = 100000;

    clock_gettime(CLOCK_MONOTONIC, &start_ts);
    setitimer(ITIMER_REAL, &itv, NULL);

    while (count < 5)
        pause();

    printf("Intervals stay anchored — no creep.\n");
    return 0;
}

/* Output shows deliveries at ~100ms, ~200ms, ~300ms...
   not accumulating error over time */

🎓 Interview Questions
Q1. What is a jiffy in the Linux kernel?

A jiffy is the duration of one kernel timer tick. It equals 1/HZ seconds. With HZ=250, one jiffy = 4ms. Timers are rounded up to the nearest jiffy boundary.

Q2. If I request a 19ms timer on a system with HZ=250 (jiffy=4ms), what is the actual delay?

The timer rounds UP to the next jiffy boundary: 20ms (5 jiffies × 4ms).

Q3. What does “timers are not subject to creeping errors” mean?

Even if one signal delivery is a few ms late, the next expiry is still scheduled at the correct absolute time. The interval is re-loaded from it_interval, anchored to the actual expiry time, not to the late delivery time.

Q4. How do you check if high-resolution timers are available on a system?

Call clock_getres(CLOCK_REALTIME, &res). If res.tv_nsec == 1, high-resolution timers (CONFIG_HIGH_RES_TIMERS) are active and provide nanosecond precision.

Q5. What kernel version introduced high-resolution timer support?

Linux 2.6.21 optionally supports high-resolution timers via CONFIG_HIGH_RES_TIMERS. When enabled, accuracy can reach the hardware limit (~1 microsecond on modern hardware).

Next: Setting Timeouts on Blocking Operations →

Part 3: Blocking Timeouts ← Part 1: Interval Timers

Leave a Reply

Your email address will not be published. Required fields are marked *