Suspending Execution and Sleeping in Linux Explained

Suspending Execution and Sleeping in Linux Explained

Suspending Execution and Sleeping in Linux Explained

🕐 sleep()
Second precision
🔬 nanosleep()
Nanosecond precision
📜 Standard
POSIX.1b

What Is Process Sleeping?

Sleeping suspends a process (or thread) for a fixed amount of time. The kernel puts the process in an unrunnable state and wakes it when the interval elapses — or earlier if a signal arrives.

Linux provides two main sleep functions: the simple sleep() and the higher-precision nanosleep().

📊 sleep() vs nanosleep() — Side-by-Side
Feature sleep() nanosleep()
Header <unistd.h> <time.h>
Precision Whole seconds only Up to 1 nanosecond
Return on interrupt Remaining seconds (unsigned int) -1 + errno=EINTR + remaining in remain
Signal interactions May interact with alarm() on some systems Safe to mix with alarm() / setitimer()
Linux implementation Calls nanosleep() internally Direct syscall
Portability concern Some systems use alarm() — avoid mixing POSIX.1b — widely supported

📋 Function Signatures
#include <unistd.h>

/* Sleep for 'seconds' seconds. Returns remaining seconds if
   interrupted by a signal, or 0 on completion. */
unsigned int sleep(unsigned int seconds);

/* ----------------------------------------- */
#define _POSIX_C_SOURCE 199309
#include <time.h>

struct timespec {
    time_t tv_sec;   /* Seconds          */
    long   tv_nsec;  /* Nanoseconds 0..999999999 */
};

/* Sleep for interval specified in 'request'.
   If interrupted, returns -1 with errno=EINTR,
   and 'remain' (if non-NULL) holds the unslept time. */
int nanosleep(const struct timespec *request,
              struct timespec *remain);
/* Returns 0 on completion, -1 on error/interrupt */

🔄 nanosleep() — Signal-Restart Loop

When a signal interrupts nanosleep(), restart it with the remaining time to complete the full sleep interval.

nanosleep(request, remain)
↓ completes normally
Returns 0 → done ✓
↓ interrupted by signal (returns -1, errno=EINTR)
remain has unslept time
Copy remain → request
nanosleep(request, remain) ← loop back

💻 Example 1: Basic sleep() Usage
#include <stdio.h>
#include <unistd.h>
#include <time.h>

int main(void) {
    time_t start, end;

    printf("Sleeping for 3 seconds...\n");
    start = time(NULL);

    unsigned int remaining = sleep(3);
    end = time(NULL);

    if (remaining == 0) {
        printf("Woke up normally. Elapsed: %ld seconds\n",
               (long)(end - start));
    } else {
        printf("Sleep interrupted! %u seconds remaining.\n",
               remaining);
    }

    /* sleep() on Linux does NOT interact with alarm() since
       it calls nanosleep() internally — safe to mix */
    return 0;
}

/* Output: Sleeping for 3 seconds...
           Woke up normally. Elapsed: 3 seconds */

💻 Example 2: nanosleep() — Sub-Second Sleeping
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <time.h>
#include <errno.h>

int main(void) {
    struct timespec req, rem;

    /* Sleep for 250 milliseconds = 0 seconds + 250,000,000 ns */
    req.tv_sec  = 0;
    req.tv_nsec = 250000000L;  /* 250 ms */

    printf("Sleeping for 250ms...\n");

    if (nanosleep(&req, &rem) == 0) {
        printf("Slept successfully.\n");
    } else {
        if (errno == EINTR) {
            printf("Interrupted! Remaining: %ld.%09ld s\n",
                   rem.tv_sec, rem.tv_nsec);
        }
    }

    /* Sleep for 1.5 seconds */
    req.tv_sec  = 1;
    req.tv_nsec = 500000000L;  /* 0.5 s */
    printf("Sleeping for 1.5 seconds...\n");
    nanosleep(&req, NULL);
    printf("Done.\n");

    return 0;
}

/* Output:
   Sleeping for 250ms...
   Slept successfully.
   Sleeping for 1.5 seconds...
   Done.
*/

💻 Example 3: Restartable nanosleep() — Full Sleep Despite Signals

Loop to ensure the full sleep interval completes even when interrupted by signals.

#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

/* A signal handler that does nothing — just interrupts nanosleep */
static void sigint_handler(int sig) {
    printf("\n[Signal %d caught — will restart sleep]\n", sig);
}

/**
 * Sleep for 'secs' seconds + 'nsecs' nanoseconds.
 * Automatically restarts if interrupted by signals.
 * Returns: 0 on success, -1 on real error.
 */
int reliable_sleep(long secs, long nsecs) {
    struct timespec req, rem;
    req.tv_sec  = secs;
    req.tv_nsec = nsecs;

    while (nanosleep(&req, &rem) == -1) {
        if (errno == EINTR) {
            /* Signal interrupted us — resume with remaining time */
            req = rem;
        } else {
            return -1;   /* Real error */
        }
    }
    return 0;
}

int main(void) {
    struct sigaction sa;

    /* Allow Ctrl-C (SIGINT) to interrupt but not kill */
    sa.sa_handler = sigint_handler;
    sa.sa_flags   = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

    printf("Sleeping for 5 seconds. Press Ctrl-C to test interruption...\n");

    if (reliable_sleep(5, 0) == 0) {
        printf("Full 5-second sleep completed.\n");
    }

    return 0;
}

/* Even if you press Ctrl-C multiple times, the program
   eventually completes the full 5-second sleep.

   Sample run:
   Sleeping for 5 seconds. Press Ctrl-C to test interruption...
   [Signal 2 caught — will restart sleep]
   [Signal 2 caught — will restart sleep]
   Full 5-second sleep completed.
*/

⚠️ Nanosecond Precision vs Real Accuracy

The precision/accuracy gap

nanosleep() accepts nanosecond values, but actual delivery is rounded up to the jiffy boundary (same as setitimer). High-res timers close this gap.

Signal-restart rounding accumulation

Each restart of nanosleep rounds up again. With very high signal rates, total sleep can drift. Use clock_nanosleep(TIMER_ABSTIME) to avoid this (covered in Part 6).

🎓 Interview Questions

Q1. What does sleep() return if interrupted by a signal?

The number of remaining (unslept) seconds. If it completed normally, it returns 0.

Q2. What is the advantage of nanosleep() over sleep()?

Nanosecond precision, explicit remaining-time via the remain argument, and guaranteed independence from alarm()/setitimer() signals (POSIX says it must not use signals internally).

Q3. Why should you NOT mix sleep() with alarm() on portable code?

On some implementations, sleep() is implemented using alarm() and SIGALRM. Calling both can corrupt timer state. On Linux specifically it is safe (sleep calls nanosleep), but for portability avoid mixing.

Q4. Write a reliable_sleep() function that handles signal interruptions.

Loop calling nanosleep() in a while loop. When it returns -1 with errno==EINTR, copy remain back to request and call again. Exit the loop when nanosleep() returns 0.

Q5. What is tv_nsec’s valid range in struct timespec?

0 to 999,999,999 (inclusive). It represents nanoseconds within the current second. Values outside this range cause nanosleep() to fail with EINVAL.

Next: POSIX Clocks — clock_gettime() & clock_settime() →

Leave a Reply

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