πŸ”¬ clock_nanosleep()

πŸ”¬ clock_nanosleep()
Chapter 23 | Part 6 of 9 β€” High-Precision Sleeping with Absolute Time
🎯 Feature
Absolute Time
πŸ”§ Flag
TIMER_ABSTIME
πŸ• Clock
Selectable

What Makes clock_nanosleep() Better?

nanosleep() is good, but has a subtle flaw: when interrupted by signals and restarted, each restart introduces rounding errors, causing oversleeping.

clock_nanosleep() solves this with absolute time mode (TIMER_ABSTIME) β€” sleep until a specific clock time, not for a relative duration. Restarts with the same absolute target never overshoot.

⚠️ The nanosleep() Oversleeping Problem
nanosleep(10s) β€” signal interrupts at T=3s, remain=7.001s (rounded up)
Restart 1: nanosleep(7.001s) β†’ signal at T=5s, remain=5.002s (rounded up again)
Restart 2: nanosleep(5.002s) β†’ signal at T=7s, remain=3.003s
Total sleep: 10s + NΓ—rounding_error β†’ longer than intended
clock_nanosleep(TIMER_ABSTIME) fix: “Sleep until T+10s” β€” no matter how many times interrupted, the final wake-up target is always T+10s. Zero drift.

πŸ“‹ Function Signature
#define _XOPEN_SOURCE 600
#include <time.h>

int clock_nanosleep(clockid_t clockid,
                    int flags,
                    const struct timespec *request,
                    struct timespec *remain);

/* Returns 0 on success,
          positive error number on error or EINTR */

/* flags:
   0            β†’ relative sleep (like nanosleep)
   TIMER_ABSTIME β†’ absolute sleep (sleep UNTIL this time)

   clockid: CLOCK_REALTIME, CLOCK_MONOTONIC,
            CLOCK_PROCESS_CPUTIME_ID, etc.

   When TIMER_ABSTIME is used, 'remain' is unused/NULL.
   Restart the call with the SAME 'request' if interrupted.

   Compile: gcc prog.c -o prog -lrt
*/

πŸ“ Relative vs Absolute Sleep Modes
Relative Mode (flags=0)

/* Sleep for 5 seconds from NOW */
struct timespec req = {5, 0};
clock_nanosleep(CLOCK_REALTIME, 0,
                &req, &remain);
/* If interrupted, req = remain
   then call again β€” drift possible */
Absolute Mode (TIMER_ABSTIME)

/* Sleep UNTIL a specific time */
clock_gettime(CLOCK_REALTIME, &abs);
abs.tv_sec += 5;
/* If interrupted, call again with
   SAME abs β€” no drift ever */
clock_nanosleep(CLOCK_REALTIME,
    TIMER_ABSTIME, &abs, NULL);

πŸ’» Example 1: Absolute Time Sleep β€” No Drift
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <errno.h>

static void handler(int sig) { (void)sig; }

int main(void) {
    struct timespec abs_target;
    struct sigaction sa;
    int s;

    /* Allow SIGINT to interrupt sleep */
    sa.sa_handler = handler;
    sa.sa_flags   = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

    /* Get current CLOCK_REALTIME value */
    clock_gettime(CLOCK_REALTIME, &abs_target);

    /* Target = now + 5 seconds */
    abs_target.tv_sec += 5;

    printf("Sleeping for 5 seconds (absolute mode)...\n");
    printf("Press Ctrl-C to interrupt β€” sleep will resume!\n");

    /* Loop to restart on interruption β€” same abs_target each time */
    while ((s = clock_nanosleep(CLOCK_REALTIME,
                                TIMER_ABSTIME,
                                &abs_target, NULL)) != 0) {
        if (s == EINTR) {
            printf("Interrupted β€” restarting (no drift)...\n");
        } else {
            perror("clock_nanosleep");
            return 1;
        }
    }

    printf("Woke up at the exact target time.\n");
    return 0;
}

/* Compile: gcc -o abs_sleep abs_sleep.c -lrt -D_XOPEN_SOURCE=600
   Press Ctrl-C multiple times β€” sleep always ends at correct time */

πŸ’» Example 2: Drift-Free Periodic Task Using Absolute Times

Run a task exactly every 100ms without any accumulating error.

#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <time.h>
#include <errno.h>

#define INTERVAL_NS  100000000L   /* 100 ms in nanoseconds */
#define ITERATIONS   10

/* Add nanoseconds to a timespec (handles carry) */
void timespec_add_ns(struct timespec *ts, long ns) {
    ts->tv_nsec += ns;
    while (ts->tv_nsec >= 1000000000L) {
        ts->tv_nsec -= 1000000000L;
        ts->tv_sec++;
    }
}

int main(void) {
    struct timespec next_wake;
    struct timespec now;
    int i, s;

    /* Set first wake-up = now + 100ms */
    clock_gettime(CLOCK_MONOTONIC, &next_wake);
    timespec_add_ns(&next_wake, INTERVAL_NS);

    for (i = 1; i <= ITERATIONS; i++) {
        /* Sleep until next_wake (absolute) */
        do {
            s = clock_nanosleep(CLOCK_MONOTONIC,
                                TIMER_ABSTIME,
                                &next_wake, NULL);
        } while (s == EINTR);   /* Restart if interrupted */

        if (s != 0) { perror("clock_nanosleep"); return 1; }

        /* Do the periodic work */
        clock_gettime(CLOCK_MONOTONIC, &now);
        printf("Tick %2d at %ld.%06ld s\n",
               i, now.tv_sec, now.tv_nsec / 1000);

        /* Advance next target by one interval */
        timespec_add_ns(&next_wake, INTERVAL_NS);
    }

    return 0;
}

/* Output (ticks at precise 100ms intervals):
   Tick  1 at 12345.100123 s
   Tick  2 at 12345.200089 s
   Tick  3 at 12345.300104 s
   ...
   Intervals stay steady β€” no accumulated error */

πŸ’» Example 3: Show the Drift Difference

Compare relative nanosleep() vs absolute clock_nanosleep() over 10 iterations.

#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <time.h>
#include <errno.h>

#define ITERS       10
#define SLEEP_NS    10000000L   /* 10 ms */

int main(void) {
    struct timespec req = {0, SLEEP_NS};
    struct timespec start, now, abs_target;
    long expected_us, actual_us;

    /* --- Method 1: relative nanosleep() --- */
    printf("=== Relative nanosleep() ===\n");
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 1; i <= ITERS; i++) {
        nanosleep(&req, NULL);
        clock_gettime(CLOCK_MONOTONIC, &now);
        actual_us   = (now.tv_sec  - start.tv_sec)  * 1000000L
                    + (now.tv_nsec - start.tv_nsec) / 1000L;
        expected_us = i * (SLEEP_NS / 1000);
        printf("Iter %2d: expected %ld Β΅s, actual %ld Β΅s, drift %+ld Β΅s\n",
               i, expected_us, actual_us, actual_us - expected_us);
    }

    /* --- Method 2: absolute clock_nanosleep() --- */
    printf("\n=== Absolute clock_nanosleep(TIMER_ABSTIME) ===\n");
    clock_gettime(CLOCK_MONOTONIC, &start);
    abs_target = start;

    for (int i = 1; i <= ITERS; i++) {
        abs_target.tv_nsec += SLEEP_NS;
        while (abs_target.tv_nsec >= 1000000000L) {
            abs_target.tv_nsec -= 1000000000L;
            abs_target.tv_sec++;
        }
        int s;
        do { s = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
                                 &abs_target, NULL);
        } while (s == EINTR);

        clock_gettime(CLOCK_MONOTONIC, &now);
        actual_us   = (now.tv_sec  - start.tv_sec)  * 1000000L
                    + (now.tv_nsec - start.tv_nsec) / 1000L;
        expected_us = i * (SLEEP_NS / 1000);
        printf("Iter %2d: expected %ld Β΅s, actual %ld Β΅s, drift %+ld Β΅s\n",
               i, expected_us, actual_us, actual_us - expected_us);
    }
    return 0;
}

/* Absolute method shows near-zero cumulative drift across all iterations */

πŸŽ“ Interview Questions
Q1. What is the key difference between nanosleep() and clock_nanosleep()?

clock_nanosleep() supports absolute time mode via TIMER_ABSTIME, and lets you choose the clock (REALTIME, MONOTONIC, etc.). nanosleep() is always relative and uses CLOCK_REALTIME implicitly.

Q2. Why does TIMER_ABSTIME prevent drift in periodic tasks?

With absolute mode, you always target a fixed future point. When interrupted and restarted, the same target is passed β€” the OS automatically handles the case where the target time has already passed (fires immediately). No rounding error accumulates.

Q3. What clock should a periodic real-time task use with clock_nanosleep()?

CLOCK_MONOTONIC β€” it never jumps, never goes backward, and is not affected by NTP adjustments. CLOCK_REALTIME could cause unexpected long sleeps if the clock jumps backward.

Q4. How do you restart clock_nanosleep() when interrupted?

With TIMER_ABSTIME: just call clock_nanosleep() again with the SAME absolute target β€” no modification needed. The remain parameter is not used. Without TIMER_ABSTIME: copy remain to request and call again.

Next: POSIX Interval Timers β€” timer_create() & friends β†’

Part 7: POSIX Interval Timers ← Part 5: POSIX Clocks

Leave a Reply

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