Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Operating Systems  Module 4 – CPU scheduling

Clocks, timers and sleeping

Wall-clock, monotonic and boot-time clocks, ticks and tickless kernels, high-resolution timers, how late a sleep wakes up, and timers in an event loop.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Clang 21.0.0, GCC 16.2.0 and Rust 1.99.0
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: Three clocks, and the one to time things with · Timers no longer wait for the tick, and idle CPUs skip it · A sleep is a minimum, not a promise · Practise: summarise a sleep measurement. With a pass: 3 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Choose between wall-clock, monotonic and boot-time clocks
  • Explain timer interrupts, tickless kernels and high-resolution timers
  • Measure how much later than requested a sleep wakes up
  • Use timerfd or kqueue timers in an event loop

Before you start

A program that times a request with the wall clock can be told that the request took −1.5 seconds, and a 5-second timeout set on the same clock can fire after 1 second. Computers keep several clocks because they answer different questions: what time it is, and how much time has passed.

This lesson picks the right clock, follows how the kernel turns its timer interrupt into timers, measures how late a sleeping thread wakes on this Mac, and builds periodic work that keeps to its schedule.

Three clocks, and the one to time things with

clock_gettime() reads several clocks, and three of them matter most. CLOCK_REALTIME is the wall clock: The clock of the time of day, CLOCK_REALTIME on Linux: seconds since the Unix epoch. It can be set, so two readings can be out of order and a duration measured on it can come out wrong., the date and time a person would read: it can be set, and its manual page says it is affected by discontinuous jumps in the system time. CLOCK_MONOTONIC can never be set: it counts from an unspecified point, the boot on Linux, and frequency adjustments can change its rate but never make it jump. It leaves out time the system spends suspended; CLOCK_BOOTTIME, Linux’s third clock, counts that too.

Clock Can it jump? Counts a suspend? Use it for
CLOCK_REALTIME Yes, when it is set Yes Dates and timestamps
CLOCK_MONOTONIC No No Durations and timeouts
CLOCK_BOOTTIME No Yes Timeouts that must count a suspend

The program models a monotonic clock: A clock that nothing can set, such as CLOCK_MONOTONIC, counting from an unspecified point, the boot on Linux. It never jumps, which makes it the clock for durations and timeouts. and a wall clock that is set while it runs, as an administrator or a time-sync daemon may set it. Its 14 versions printed the same lines:

One program · 14 languages A job and a timeout, timed on the wall clock and on the monotonic clock

Python 3.14 · main.py

# A job and a timeout, each timed on the wall clock and on the monotonic clock while
# the wall clock is set. All times are in ms of real time.


def wall(t: int, set_at: int, set_by: int) -> int:
    """The wall clock at real time t, moved by set_by ms at set_at."""
    return t + (set_by if t >= set_at else 0)


def monotonic(t: int) -> int:
    """The monotonic clock at real time t: nothing can set it."""
    return t


def fires(clock, limit: int) -> int:
    """The real time at which a deadline limit ms ahead on clock is reached."""
    t = 0
    while clock(t) < clock(0) + limit:
        t += 1
    return t


print("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms")
print(f"  on the wall clock it took {wall(1000, 400, -2500) - wall(0, 400, -2500)} ms")
print(f"  on the monotonic clock it took {monotonic(1000) - monotonic(0)} ms")
print("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms")
print(
    f"  on the wall clock it fires at {fires(lambda t: wall(t, 1000, 3600000), 5000)} ms"
)
print(f"  on the monotonic clock it fires at {fires(monotonic, 5000)} ms")

In Python: fires takes the clock as a function, so the wall clock of the timeout is a lambda that fixes when and how far it is set.

Recorded with CPython 3.14.8 on macOS 26 arm64. It runs in your browser with Pyodide 314.0.7 (CPython 3.14.2). To run it yourself: python3 main.py

JavaScript Node 24 · main.mjs

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

// The wall clock at real time t, moved by setBy ms at setAt.
const wall = (t, setAt, setBy) => t + (t >= setAt ? setBy : 0);

// The monotonic clock at real time t: nothing can set it.
const monotonic = (t) => t;

// The real time at which a deadline limit ms ahead on clock is reached.
function fires(clock, limit) {
  let t = 0;
  while (clock(t) < clock(0) + limit) t++;
  return t;
}

console.log("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms");
console.log(`  on the wall clock it took ${wall(1000, 400, -2500) - wall(0, 400, -2500)} ms`);
console.log(`  on the monotonic clock it took ${monotonic(1000) - monotonic(0)} ms`);
console.log("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms");
console.log(`  on the wall clock it fires at ${fires((t) => wall(t, 1000, 3600000), 5000)} ms`);
console.log(`  on the monotonic clock it fires at ${fires(monotonic, 5000)} ms`);

In JavaScript: Each clock is an arrow function of the real time, and fires takes either one.

Recorded with Node.js 24.21.0 LTS (V8 13.6) on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0). To run it yourself: node main.mjs

Java 25 LTS · Main.java

import java.util.function.IntUnaryOperator;

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.
public class Main {
  // The wall clock at real time t, moved by setBy ms at setAt.
  static int wall(int t, int setAt, int setBy) {
    return t + (t >= setAt ? setBy : 0);
  }

  // The monotonic clock at real time t: nothing can set it.
  static int monotonic(int t) {
    return t;
  }

  // The real time at which a deadline limit ms ahead on clock is reached.
  static int fires(IntUnaryOperator clock, int limit) {
    int t = 0;
    while (clock.applyAsInt(t) < clock.applyAsInt(0) + limit) {
      t++;
    }
    return t;
  }

  public static void main(String[] args) {
    int took = wall(1000, 400, -2500) - wall(0, 400, -2500);
    System.out.println("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms");
    System.out.println("  on the wall clock it took " + took + " ms");
    System.out.println(
        "  on the monotonic clock it took " + (monotonic(1000) - monotonic(0)) + " ms");
    int fired = fires(t -> wall(t, 1000, 3600000), 5000);
    System.out.println("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms");
    System.out.println("  on the wall clock it fires at " + fired + " ms");
    System.out.println(
        "  on the monotonic clock it fires at " + fires(Main::monotonic, 5000) + " ms");
  }
}

In Java: fires takes an IntUnaryOperator, so the monotonic clock goes in as the method reference Main::monotonic.

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. It runs in your browser with a TeaVM 0.16.0 build in QuickJS (quickjs-emscripten 0.32.0). To run it yourself: java Main.java

C++23 · main.cpp

#include <functional>
#include <print>

// A job and a timeout, each timed on the wall clock and on the monotonic clock
// while the wall clock is set. All times are in ms of real time.

// The wall clock at real time t, moved by set_by ms at set_at.
int wall(int t, int set_at, int set_by) {
  return t + (t >= set_at ? set_by : 0);
}

// The monotonic clock at real time t: nothing can set it.
int monotonic(int t) { return t; }

// The real time at which a deadline limit ms ahead on clock is reached.
int fires(const std::function<int(int)> &clock, int limit) {
  int t = 0;
  while (clock(t) < clock(0) + limit) {
    ++t;
  }
  return t;
}

int main() {
  const int took = wall(1000, 400, -2500) - wall(0, 400, -2500);
  std::println("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms");
  std::println("  on the wall clock it took {} ms", took);
  std::println("  on the monotonic clock it took {} ms",
               monotonic(1000) - monotonic(0));
  const int fired = fires([](int t) { return wall(t, 1000, 3600000); }, 5000);
  std::println(
      "a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms");
  std::println("  on the wall clock it fires at {} ms", fired);
  std::println("  on the monotonic clock it fires at {} ms",
               fires(monotonic, 5000));
}

In C++: fires takes a std::function<int(int)>, which holds the lambda of the set wall clock as well as the plain function monotonic.

Recorded with Apple clang 21.0.0 (C++23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.cpp -o main && ./main

C# 14 · .NET 10 · app.cs

using static System.FormattableString;

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

int took = Wall(1000, 400, -2500) - Wall(0, 400, -2500);
int tookMono = Monotonic(1000) - Monotonic(0);
int fired = Fires(t => Wall(t, 1000, 3600000), 5000);
int firedMono = Fires(Monotonic, 5000);
// Invariant formats numbers alike in every locale: -1500, not the −1500 of some.
Console.WriteLine("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms");
Console.WriteLine(Invariant($"  on the wall clock it took {took} ms"));
Console.WriteLine(Invariant($"  on the monotonic clock it took {tookMono} ms"));
Console.WriteLine("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms");
Console.WriteLine(Invariant($"  on the wall clock it fires at {fired} ms"));
Console.WriteLine(Invariant($"  on the monotonic clock it fires at {firedMono} ms"));

// The wall clock at real time t, moved by setBy ms at setAt.
static int Wall(int t, int setAt, int setBy) => t + (t >= setAt ? setBy : 0);

// The monotonic clock at real time t: nothing can set it.
static int Monotonic(int t) => t;

// The real time at which a deadline limit ms ahead on clock is reached.
static int Fires(Func<int, int> clock, int limit)
{
    int t = 0;
    while (clock(t) < clock(0) + limit)
    {
        t++;
    }
    return t;
}

In C#: FormattableString.Invariant prints -1500 alike in every locale, where some would write the minus sign as −; fires takes a Func<int, int>.

Recorded with C# 14 on .NET 10.0.12 (SDK 10.0.401) on macOS 26 arm64. It runs in your browser with the .NET runtime for WebAssembly 10.0.12. To run it yourself: dotnet run app.cs

C23 · main.c

#include <stdio.h>

// A job and a timeout, each timed on the wall clock and on the monotonic clock
// while the wall clock is set. All times are in ms of real time.

// The wall clock at real time t, moved by set_by ms at set_at.
static int wall(int t, int set_at, int set_by) {
  return t + (t >= set_at ? set_by : 0);
}

// The wall clock of the timeout: set forward an hour at 1000 ms.
static int wall_forward(int t) { return wall(t, 1000, 3600000); }

// The monotonic clock at real time t: nothing can set it.
static int monotonic(int t) { return t; }

// The real time at which a deadline limit ms ahead on clock is reached.
static int fires(int (*clock)(int), int limit) {
  int t = 0;
  while (clock(t) < clock(0) + limit) {
    t++;
  }
  return t;
}

int main(void) {
  puts("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms");
  printf("  on the wall clock it took %d ms\n",
         wall(1000, 400, -2500) - wall(0, 400, -2500));
  printf("  on the monotonic clock it took %d ms\n",
         monotonic(1000) - monotonic(0));
  puts(
      "a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms");
  printf("  on the wall clock it fires at %d ms\n", fires(wall_forward, 5000));
  printf("  on the monotonic clock it fires at %d ms\n",
         fires(monotonic, 5000));
  return 0;
}

In C: C has no closures, so the wall clock of the timeout is a function of its own, wall_forward, passed to fires as a pointer.

Recorded with Apple clang 21.0.0 (C23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.c -o main && ./main

PHP 8.5 · main.php

<?php

declare(strict_types=1);

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

/** The wall clock at real time $t, moved by $setBy ms at $setAt. */
function wall(int $t, int $setAt, int $setBy): int
{
    return $t + ($t >= $setAt ? $setBy : 0);
}

/** The monotonic clock at real time $t: nothing can set it. */
function monotonic(int $t): int
{
    return $t;
}

/** The real time at which a deadline $limit ms ahead on $clock is reached. */
function fires(callable $clock, int $limit): int
{
    $t = 0;
    while ($clock($t) < $clock(0) + $limit) {
        $t++;
    }
    return $t;
}

echo "a 1000 ms job; at 400 ms the wall clock is set back 2500 ms\n";
printf("  on the wall clock it took %d ms\n", wall(1000, 400, -2500) - wall(0, 400, -2500));
printf("  on the monotonic clock it took %d ms\n", monotonic(1000) - monotonic(0));
echo "a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms\n";
$forward = fn(int $t): int => wall($t, 1000, 3600000);
printf("  on the wall clock it fires at %d ms\n", fires($forward, 5000));
printf("  on the monotonic clock it fires at %d ms\n", fires(monotonic(...), 5000));

In PHP: monotonic(...) is the first-class callable syntax of PHP 8.1, and the arrow function $forward fixes the setting of the wall clock.

Recorded with PHP 8.5.11 (CLI) on macOS 26 arm64. It runs in your browser with php-wasm 0.2.0 (PHP 8.5.2). To run it yourself: php main.php

Rust 1.99 · main.rs

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

/// The wall clock at real time t, moved by set_by ms at set_at.
fn wall(t: i64, set_at: i64, set_by: i64) -> i64 {
    t + if t >= set_at { set_by } else { 0 }
}

/// The monotonic clock at real time t: nothing can set it.
fn monotonic(t: i64) -> i64 {
    t
}

/// The real time at which a deadline limit ms ahead on clock is reached.
fn fires(clock: impl Fn(i64) -> i64, limit: i64) -> i64 {
    let mut t = 0;
    while clock(t) < clock(0) + limit {
        t += 1;
    }
    t
}

fn main() {
    let took = wall(1000, 400, -2500) - wall(0, 400, -2500);
    println!("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms");
    println!("  on the wall clock it took {took} ms");
    println!(
        "  on the monotonic clock it took {} ms",
        monotonic(1000) - monotonic(0)
    );
    let fired = fires(|t| wall(t, 1000, 3_600_000), 5000);
    println!("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms");
    println!("  on the wall clock it fires at {fired} ms");
    println!(
        "  on the monotonic clock it fires at {} ms",
        fires(monotonic, 5000)
    );
}

In Rust: fires takes any impl Fn(i64) -> i64, a closure or the function monotonic alike.

Recorded with rustc 1.99.0 (edition 2024) on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: rustc --edition 2024 -O main.rs && ./main

TypeScript 6.0 · main.ts

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

// The wall clock at real time t, moved by setBy ms at setAt.
const wall = (t: number, setAt: number, setBy: number): number => t + (t >= setAt ? setBy : 0);

// The monotonic clock at real time t: nothing can set it.
const monotonic = (t: number): number => t;

// The real time at which a deadline limit ms ahead on clock is reached.
function fires(clock: (t: number) => number, limit: number): number {
  let t = 0;
  while (clock(t) < clock(0) + limit) t++;
  return t;
}

console.log("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms");
console.log(`  on the wall clock it took ${wall(1000, 400, -2500) - wall(0, 400, -2500)} ms`);
console.log(`  on the monotonic clock it took ${monotonic(1000) - monotonic(0)} ms`);
console.log("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms");
console.log(`  on the wall clock it fires at ${fires((t) => wall(t, 1000, 3600000), 5000)} ms`);
console.log(`  on the monotonic clock it fires at ${fires(monotonic, 5000)} ms`);

In TypeScript: The clocks are typed (t: number) => number, which is also the type fires asks for.

Recorded with TypeScript 6.0.3 (strict), run by Node.js 24.21.0 on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0) after Sucrase 3.35.1. To run it yourself: npx -p typescript@6.0.3 tsc --noEmit --strict main.ts && node main.ts

Go 1.27 · main.go

package main

import "fmt"

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

// wall is the wall clock at real time t, moved by setBy ms at setAt.
func wall(t, setAt, setBy int) int {
	if t >= setAt {
		return t + setBy
	}
	return t
}

// monotonic is the monotonic clock at real time t: nothing can set it.
func monotonic(t int) int { return t }

// fires returns the real time at which a deadline limit ms ahead on clock is reached.
func fires(clock func(int) int, limit int) int {
	t := 0
	for clock(t) < clock(0)+limit {
		t++
	}
	return t
}

func main() {
	fmt.Println("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms")
	fmt.Printf("  on the wall clock it took %d ms\n", wall(1000, 400, -2500)-wall(0, 400, -2500))
	fmt.Printf("  on the monotonic clock it took %d ms\n", monotonic(1000)-monotonic(0))
	fmt.Println("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms")
	forward := func(t int) int { return wall(t, 1000, 3600000) }
	fmt.Printf("  on the wall clock it fires at %d ms\n", fires(forward, 5000))
	fmt.Printf("  on the monotonic clock it fires at %d ms\n", fires(monotonic, 5000))
}

In Go: fires takes a func(int) int, so forward is a closure and monotonic a plain function.

Recorded with Go 1.27.1 on macOS 26 arm64. It runs in your browser with the Go compiler in WebAssembly 1.27.1. To run it yourself: go run main.go

Swift 6.4 · main.swift

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

/// The wall clock at real time t, moved by setBy ms at setAt.
func wall(_ t: Int, _ setAt: Int, _ setBy: Int) -> Int { t + (t >= setAt ? setBy : 0) }

/// The monotonic clock at real time t: nothing can set it.
func monotonic(_ t: Int) -> Int { t }

/// The real time at which a deadline limit ms ahead on clock is reached.
func fires(_ clock: (Int) -> Int, _ limit: Int) -> Int {
  var t = 0
  while clock(t) < clock(0) + limit { t += 1 }
  return t
}

print("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms")
print("  on the wall clock it took \(wall(1000, 400, -2500) - wall(0, 400, -2500)) ms")
print("  on the monotonic clock it took \(monotonic(1000) - monotonic(0)) ms")
print("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms")
print("  on the wall clock it fires at \(fires({ wall($0, 1000, 3_600_000) }, 5000)) ms")
print("  on the monotonic clock it fires at \(fires(monotonic, 5000)) ms")

In Swift: fires takes a closure of type (Int) -> Int, written { wall($0, 1000, 3_600_000) } with its shorthand argument.

Recorded with Apple Swift 6.4 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: swiftc -O main.swift -o main && ./main

Ruby 4.0 · main.rb

# frozen_string_literal: true

# A job and a timeout, each timed on the wall clock and on the monotonic clock while
# the wall clock is set. All times are in ms of real time.

# The wall clock at a real time, moved by set_by ms at set_at.
def wall(time, set_at, set_by) = time + (time >= set_at ? set_by : 0)

# The monotonic clock at a real time: nothing can set it.
def monotonic(time) = time

# The real time at which a deadline limit ms ahead on clock is reached.
def fires(clock, limit)
  time = 0
  time += 1 while clock.call(time) < clock.call(0) + limit
  time
end

puts 'a 1000 ms job; at 400 ms the wall clock is set back 2500 ms'
puts "  on the wall clock it took #{wall(1000, 400, -2500) - wall(0, 400, -2500)} ms"
puts "  on the monotonic clock it took #{monotonic(1000) - monotonic(0)} ms"
puts 'a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms'
puts "  on the wall clock it fires at #{fires(->(t) { wall(t, 1000, 3_600_000) }, 5000)} ms"
puts "  on the monotonic clock it fires at #{fires(method(:monotonic), 5000)} ms"

In Ruby: The clocks are endless methods; fires takes a lambda or method(:monotonic), and both answer call.

Recorded with CRuby 4.0.7 on macOS 26 arm64. It runs in your browser with ruby.wasm 2.10.1 (Ruby 4.0.0). To run it yourself: ruby main.rb

Kotlin 2.4 · Main.kt

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

// The wall clock at real time t, moved by setBy ms at setAt.
fun wall(
    t: Int,
    setAt: Int,
    setBy: Int,
): Int = t + if (t >= setAt) setBy else 0

// The monotonic clock at real time t: nothing can set it.
fun monotonic(t: Int): Int = t

// The real time at which a deadline limit ms ahead on clock is reached.
fun fires(
    clock: (Int) -> Int,
    limit: Int,
): Int {
    var t = 0
    while (clock(t) < clock(0) + limit) t++
    return t
}

fun main() {
    println("a 1000 ms job; at 400 ms the wall clock is set back 2500 ms")
    println("  on the wall clock it took ${wall(1000, 400, -2500) - wall(0, 400, -2500)} ms")
    println("  on the monotonic clock it took ${monotonic(1000) - monotonic(0)} ms")
    println("a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms")
    println("  on the wall clock it fires at ${fires({ wall(it, 1000, 3_600_000) }, 5000)} ms")
    println("  on the monotonic clock it fires at ${fires(::monotonic, 5000)} ms")
}

In Kotlin: fires takes a function type (Int) -> Int, given as a lambda or as the reference ::monotonic.

Recorded with Kotlin 2.4.20 on Java 25.0.4.1 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar

Dart 3.13 · main.dart

// A job and a timeout, each timed on the wall clock and on the monotonic clock while
// the wall clock is set. All times are in ms of real time.

/// The wall clock at real time [t], moved by [setBy] ms at [setAt].
int wall(int t, int setAt, int setBy) => t + (t >= setAt ? setBy : 0);

/// The monotonic clock at real time [t]: nothing can set it.
int monotonic(int t) => t;

/// The real time at which a deadline [limit] ms ahead on [clock] is reached.
int fires(int Function(int) clock, int limit) {
  var t = 0;
  while (clock(t) < clock(0) + limit) {
    t++;
  }
  return t;
}

void main() {
  print('a 1000 ms job; at 400 ms the wall clock is set back 2500 ms');
  print(
    '  on the wall clock it took ${wall(1000, 400, -2500) - wall(0, 400, -2500)} ms',
  );
  print(
    '  on the monotonic clock it took ${monotonic(1000) - monotonic(0)} ms',
  );
  print(
    'a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms',
  );
  print(
    '  on the wall clock it fires at ${fires((t) => wall(t, 1000, 3600000), 5000)} ms',
  );
  print('  on the monotonic clock it fires at ${fires(monotonic, 5000)} ms');
}

In Dart: fires takes an int Function(int), and Dart passes the top-level function monotonic as it is.

Recorded with Dart 3.13.5 on macOS 26 arm64. It runs in your browser with a WebAssembly build by dart2wasm 3.13.5. To run it yourself: dart run main.dart

What every language printed

a 1000 ms job; at 400 ms the wall clock is set back 2500 ms
  on the wall clock it took -1500 ms
  on the monotonic clock it took 1000 ms
a 5000 ms timeout; at 1000 ms the wall clock is set forward 3600000 ms
  on the wall clock it fires at 1000 ms
  on the monotonic clock it fires at 5000 ms

Set back 2.5 s during a 1 s job, the wall clock measures −1,500 ms, and set forward an hour, it fires a 5 s timeout after 1 s. The monotonic clock gets both right. Rust’s standard library draws the same line: SystemTime, the wall clock, is not monotonic, and Instant is the clock for measuring elapsed time. Pick the clock for a reading of your own in the table:

Interactive · answer and compare Which clock should this code read? Answer two questions about the reading. The table marks the clocks that fit and says why the others do not.
What will the reading be used for?
Should time spent suspended count?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Four Linux clocks, against the four things that decide between them
Clock Can it jump Does it count a suspend What a reading means What it is for
The wall clock Read with: clock_gettime(CLOCK_REALTIME) Poor yes: setting the clock moves it, forward or back Good yes: it shows the time of day Good seconds since the Unix epoch, comparable between machines Mixed timestamps in logs, files and messages
The monotonic clock Read with: clock_gettime(CLOCK_MONOTONIC) Good never; only its rate may be adjusted Mixed no: a suspend is left out Mixed time since an unspecified point, on Linux the boot Good durations and timeouts
The boot-time clock Read with: clock_gettime(CLOCK_BOOTTIME) Good never Good yes: the same clock, with suspends counted Mixed time since the boot, suspends included Good timeouts that must count a suspend
The process's CPU-time clock Read with: clock_gettime(CLOCK_PROCESS_CPUTIME_ID) Good never; it cannot be set on Linux Poor no: it counts only CPU time the process used Mixed CPU time of all the threads of the process Mixed how much computing a piece of work cost
How the answers decide
  • “What will the reading be used for?” How long something took, or a timeout: rules out The wall clock and The process's CPU-time clock. A duration needs a clock that never jumps and counts all the time that passes, not only CPU time.
  • “What will the reading be used for?” A date and time to show or to store: rules out The monotonic clock, The boot-time clock and The process's CPU-time clock. Only the wall clock has a meaning outside this boot of this machine.
  • “What will the reading be used for?” How much CPU a computation used: rules out The wall clock, The monotonic clock and The boot-time clock. The elapsed clocks include time spent waiting; the CPU-time clock counts only the work.
  • “What will the reading be used for?” How long something took, or a timeout and “Should time spent suspended count?” Yes, such as a lease that expires in real time: rules out The monotonic clock. CLOCK_MONOTONIC stops counting while the system is suspended, and CLOCK_BOOTTIME goes on.
  • “What will the reading be used for?” How long something took, or a timeout and “Should time spent suspended count?” No, such as how long a request ran: adds a caveat to The boot-time clock. CLOCK_BOOTTIME would count a suspend as running time, so a request could time out while the machine slept.

Key idea

Measure durations and set timeouts on CLOCK_MONOTONIC, which nothing can set, and keep CLOCK_REALTIME for dates and timestamps.

Checkpoint

Check: Wall-clock, monotonic and boot-time clocks

  1. Question 1 of 2 Which clock should a 5 s request timeout use on Linux?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: CLOCK_MONOTONIC

    Durations and timeouts belong on the monotonic clock. In the program, a timeout on the wall clock fired after 1 s when the clock was set forward an hour.

  2. Question 2 of 2 What is the difference between CLOCK_MONOTONIC and CLOCK_BOOTTIME on Linux?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: CLOCK_BOOTTIME also counts time the system spends suspended

    clock_gettime(2) calls CLOCK_BOOTTIME identical to CLOCK_MONOTONIC, except that it also includes any time that the system is suspended.

Timers no longer wait for the tick, and idle CPUs skip it

Kernels kept time with a timer interrupt: An interrupt raised by a hardware timer the kernel set. It stops the running program and makes the core enter the kernel, which can then give the CPU to another program. More in “The hardware an operating system manages” at a fixed rate, HZ, which time(7) gives as 100, 250 (the default), 300 or 1000 a second. A jiffy: One tick of the kernel's periodic timer interrupt: 1 ÷ HZ seconds, so 4 ms when HZ is 250. Before high-resolution timers, sleeps and timeouts could be no finer than a jiffy., one tick, is then 10, 4, 3.3 or 1 ms, and sleeps and timeouts could be no finer. Linux 2.6.21 changed both. high-resolution timer: A kernel timer that expires as precisely as the hardware allows, microseconds on modern machines, rather than on the next tick; Linux has them from 2.6.21 as hrtimers. now expire as precisely as the hardware allows, microseconds on modern machines. The hrtimers document keeps them in a red-black tree ordered by expiry, beside the timer wheel that serves timeouts, most of which are cancelled before they fire. And with dynticks, an idle CPU stops its periodic tick and programs the timer for its next event. The kernel’s NO_HZ document lists three modes:

Mode Ticks are left out Why
CONFIG_HZ_PERIODIC never the simplest
CONFIG_NO_HZ_IDLE on idle CPUs saves energy, and should be the default
CONFIG_NO_HZ_FULL also on CPUs with one runnable task lets that task run nearly uninterrupted

A tickless kernel: A kernel that leaves out the periodic timer interrupt where it is not needed: on idle CPUs, and with CONFIG_NO_HZ_FULL on CPUs with one runnable task. in the third mode still gives such a CPU a tick about once a second.

Checkpoint

Check: Timer interrupts, tickless kernels and high-resolution timers

  1. Question 1 of 2 A kernel is built with HZ = 250. How long is a jiffy, in ms?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 4 ms

    250 ticks a second make each tick 1000 ÷ 250 = 4 ms. With HZ = 1000 a jiffy is 1 ms, and with HZ = 100, 10 ms.

  2. Question 2 of 2 What does a CONFIG_NO_HZ_IDLE kernel do on a CPU that has nothing to run?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It stops the CPU's periodic tick and programs the timer for the next event the CPU needs

    Dynticks, from Linux 2.6.21, let an idle CPU skip ticks it does not need. CONFIG_NO_HZ_FULL goes further, for CPUs with one runnable task.

A sleep is a minimum, not a promise

nanosleep(2) suspends a thread until at least the requested time has elapsed, rounded up to the clock’s granularity, and warns that there may then be a further delay before the CPU is free to run the thread. On Linux, timer slack: The time by which Linux may delay a thread's timer wake-ups so that it can handle several at once and save power: 50 µs inherited from init unless changed, none for real-time threads. adds one more: the kernel may wake a sleeping thread up to 50 µs late, the slack of init that every process inherits unless it changes it, so that several timers can be handled in one wake-up. Threads under a real-time policy get no slack. The program asks this Mac for a 1 ms sleep 200 times and times each one on the monotonic clock:

200 sleeps of 1 ms, timed on the monotonic clock Rust · sleep-overshoot/sleep_overshoot.rs
//! Asks for a 1 ms sleep 200 times and measures, on the monotonic clock, how much
//! longer than 1 ms each one took.
use std::thread::sleep;
use std::time::{Duration, Instant};

fn main() {
    let asked = Duration::from_millis(1);
    let took: Vec<Duration> = (0..200)
        .map(|_| {
            let start = Instant::now();
            sleep(asked);
            start.elapsed()
        })
        .collect();
    let never_short = took.iter().all(|&d| d >= asked);
    let mut over: Vec<u128> = took
        .iter()
        .map(|&d| d.saturating_sub(asked).as_micros())
        .collect();
    over.sort_unstable();
    // The nearest-rank percentile: the smallest overshoot that p % of the sleeps reach.
    let rank = |p: usize| over[(over.len() * p).div_ceil(100) - 1];
    println!("asked for a 1 ms sleep 200 times");
    println!(
        "every sleep lasted at least 1 ms: {}",
        if never_short { "yes" } else { "no" }
    );
    println!("overshoot, median: {} µs", rank(50));
    println!("overshoot, 99th percentile: {} µs", rank(99));
    println!("overshoot, worst: {} µs", over[over.len() - 1]);
}

Output

asked for a 1 ms sleep 200 times
every sleep lasted at least 1 ms: yes
overshoot, median: 318 µs
overshoot, 99th percentile: 2927 µs
overshoot, worst: 3666 µs

This output changes from run to run: how late each sleep wakes depends on what else the machine is doing; that no sleep ends early does not

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O sleep_overshoot.rs && ./sleep_overshoot

Every sleep lasted at least 1 ms, as the documentation of Rust’s sleep promises, but in the recorded run the median sleep overshot by 318 µs, about a third of a millisecond, and one in a hundred by almost 3 ms. A loop that must keep time cannot rely on sleeping for an interval: it sleeps until an absolute deadline instead, with clock_nanosleep() and TIMER_ABSTIME. Event loops wait for timers the way they wait for sockets: Linux’s timerfd_create() makes a timer that poll() and epoll can watch, and a kqueue takes EVFILT_TIMER events on macOS and the BSDs.

Checkpoint

Check: How long a sleep lasts, and timer slack

  1. Question 1 of 2 A thread asks nanosleep() for 1 ms. What can it count on?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It sleeps at least 1 ms, rounded up to the clock's granularity, and may wait longer before it runs again

    nanosleep(2) guarantees at least the requested time and warns of a further delay before the CPU runs the thread again; the recorded run shows that delay.

  2. Question 2 of 2 By how many µs may Linux delay the timer wake-ups of a normal thread, with the slack every process inherits from init?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 50 µs

    init's timer slack is 50,000 ns, and new threads take the slack of the thread that created them, so a thread that changes nothing may wake up to 50 µs late.

Practise: summarise a sleep measurement

Write overshoot_report(asked_us, took_us), which turns measured sleeps into the median, the 99th percentile and the worst overshoot. Its sample tests include five sleeps, a hundred, and a sleep that ended early, which cannot happen:

Exercise · Easy · Python

Summarise how late a set of sleeps woke up

Write overshoot_report(asked_us, took_us) in report.py. asked_us is the time each sleep asked for and took_us lists how long each of at least one sleep really took, both in microseconds. Return (median, p99, worst): the overshoots took - asked at the 50th and the 99th nearest-rank percentile, and the largest overshoot. The nearest-rank p-th percentile of n values sorted from small to large is the one at position ⌈p × n ÷ 100⌉, counting from 1. A sleep never ends early, so raise ValueError if any sleep took less than it asked for.

Five sleeps of 1 ms that took 1,050, 1,100, 1,060, 1,200 and 1,080 µs overshot by 50, 100, 60, 200 and 80 µs, so overshoot_report(1000, [1050, 1100, 1060, 1200, 1080]) returns (80, 200, 200).

Starter code · report.py

def overshoot_report(asked_us, took_us):
    """(median, p99, worst) overshoot in µs of sleeps that asked for asked_us each."""
    # Replace this line with your code.
    return (0, 0, 0)
The sample tests · test_report.py
from report import overshoot_report


def test_five_sleeps():
    """takes the third of five sorted overshoots as the median and the fifth as the 99th percentile"""
    assert overshoot_report(1000, [1050, 1100, 1060, 1200, 1080]) == (80, 200, 200)


def test_no_overshoot():
    """reports zeros when every sleep took exactly what it asked for"""
    assert overshoot_report(1000, [1000, 1000, 1000]) == (0, 0, 0)


def test_a_hundred_sleeps():
    """takes the 50th and the 99th of a hundred overshoots of 1 to 100 µs"""
    assert overshoot_report(1000, [1000 + i for i in range(1, 101)]) == (50, 99, 100)


def test_a_sleep_that_ended_early():
    """refuses a measurement in which a sleep ended before its time"""
    try:
        overshoot_report(1000, [1000, 999])
    except ValueError:
        return
    raise AssertionError("a sleep of 999 µs for 1000 µs should raise ValueError")
A hint

Sort the overshoots first. With n of them, the p-th percentile is at index math.ceil(p * n / 100) - 1 of the sorted list, because Python counts from 0, and the worst is the last one. Check for a negative overshoot before you return.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

The self-check below covers all three ideas, weakest first.

Practice that adapts to you

Practise this lesson

10 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 A program measures a job as having taken −1,500 ms. What is the likely cause?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: It timed the job with the wall clock, which was set back while the job ran

    In the program, setting the wall clock back 2.5 s during a 1 s job gave −1,500 ms, while the monotonic clock measured 1,000 ms.

  2. Question 2 of 10 A 5 s timeout whose deadline is kept on the wall clock fires after 1 s. What happened?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: The wall clock was set forward, past the deadline

    The program set the wall clock forward an hour at 1 s, and the deadline fired at once; on the monotonic clock it fired at 5 s.

  3. Question 3 of 10 In Rust's standard library, which type should time how long a function runs?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: std::time::Instant

    Rust draws the same line as clock_gettime(): SystemTime for dates, Instant for durations.

  4. Question 4 of 10 Can a time-sync daemon change CLOCK_MONOTONIC on Linux?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: It can adjust its rate, but it can never make it jump

    A rate adjustment keeps the clock moving forward at a slightly different speed, which keeps durations sensible.

  5. Question 5 of 10 With HZ = 1000 and a periodic tick, how many timer interrupts does a CPU take each second?

    Type a number.

    Show the answer: Question 5 of 10

    Answer: 1000 interrupts

    HZ is the tick rate itself, so the CPU is interrupted 1000 times a second, once a millisecond, even when it has nothing to do.

  6. Question 6 of 10 What did high-resolution timers change for nanosleep() and other timers?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: Their accuracy is no longer limited by the jiffy, and can be as fine as the hardware allows

    Since Linux 2.6.21, hrtimers keep precise timers in a red-black tree ordered by expiry, apart from the wheel of timeouts.

  7. Question 7 of 10 On a CONFIG_NO_HZ_FULL kernel, which CPUs can go without their periodic tick?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: Idle CPUs, and CPUs with a single runnable task, which still get a tick about once a second

    With one runnable task there is nothing to switch to, so the tick can mostly stop; at least one CPU keeps its tick for the kernel's timekeeping.

  8. Question 8 of 10 How did the 200 sleeps of 1 ms in the recorded run compare with what they asked for?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: Each lasted at least 1 ms, and most overshot by a fraction of a millisecond, a few by much more

    The median and the 99th percentile differ several times over, which is why a loop that must keep time needs absolute deadlines rather than sleeps alone.

  9. Question 9 of 10 Which threads get no timer slack on Linux?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: Threads under a real-time scheduling policy

    A real-time thread's wake-ups are not deferred to group them with others, because its latency matters more than the power saved.

  10. Question 10 of 10 How does an event loop on Linux wait for a timer together with its sockets?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: Through a timerfd, a timer with a file descriptor that poll() and epoll can watch

    On macOS and the BSDs a kqueue does the same with EVFILT_TIMER events.

Periodic work that does not drift Pro: opens with a Pro, Premium or Ultimate pass

A loop that sleeps a period after each run falls behind by its work and its wake-up delay every time. This part runs that drift in a model, removes it with absolute deadlines and chooses what to do when a run takes longer than its period.

In this part:

  • Relative sleeps and absolute deadlines, side by side
  • Sleeping until a deadline
  • When a run takes longer than its period

1 runnable example · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Timers in an event loop with timerfd and kqueue Pro: opens with a Pro, Premium or Ultimate pass

An event loop waits for many things at once, so a timer has to arrive the way a socket’s data does. This part sets one up with timerfd on Linux and with kqueue on this Mac, and runs the kqueue version.

In this part:

  • timerfd on Linux: a timer you can poll
  • kqueue on macOS and the BSDs, run on this Mac

1 runnable example (C) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

What a clock read costs, and clocks in containers Pro: opens with a Pro, Premium or Ultimate pass

Programs read the clock millions of times, and containers move between machines with their clocks running. This part measures a clock read against a system call and shows how Linux keeps monotonic time steady when a container moves.

In this part:

  • A clock read without a system call
  • Time namespaces: monotonic time that survives a move
  • Timer slack in detail

1 diagram · 1 runnable example (Rust) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    Why should a program measure how long something takes with a monotonic clock rather than the wall clock?

    The wall clock, CLOCK_REALTIME on Linux, tells the time of day, and it can be set: by an administrator, or stepped by a time-sync daemon. A duration taken as the difference of two wall-clock readings includes any such change, so it can come out negative or far too long, and a timeout whose deadline is on the wall clock can fire early or late. A monotonic clock such as CLOCK_MONOTONIC cannot be set and never jumps. It counts from an unspecified point, the boot on Linux, so its readings mean nothing as dates, but the difference of two readings is a true duration. In the lesson's model, a 1 s job timed on the wall clock while it was set back 2.5 s came out as −1,500 ms, and the monotonic clock said 1,000 ms.

  2. Mid to senior

    A service's latency dashboard sometimes shows negative request times, and a few timeouts fire at once, always around the moments its servers sync their clocks. What is wrong, how do you fix it, and how do you keep the timestamps in its logs comparable between servers?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A loop reads a sensor and then sleeps 10 ms, to sample at 100 Hz. After an hour the samples are well behind schedule, and the gaps between them vary. Explain why, and rewrite the loop so that it keeps time.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A Linux server holds 100,000 connections, each with an idle timeout of 30 s that restarts on every message, and runs a few precise periodic jobs. How would you implement the timers of its event loop, and on which clock?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Time durations and timeouts with CLOCK_MONOTONIC, or CLOCK_BOOTTIME when a suspend must count; the wall clock can be set, and then measures −1,500 ms for a 1 s job.
  • Linux’s high-resolution timers expire as precisely as the hardware allows, and tickless kernels leave the periodic tick out on idle CPUs and, with CONFIG_NO_HZ_FULL, on CPUs with one task.
  • A sleep lasts at least what it asked for and often more: on this Mac a 1 ms sleep overshot by 318 µs at the median in the recorded run.

Terms in this lesson

high-resolution timer
A kernel timer that expires as precisely as the hardware allows, microseconds on modern machines, rather than on the next tick; Linux has them from 2.6.21 as hrtimers.
jiffy
One tick of the kernel's periodic timer interrupt: 1 ÷ HZ seconds, so 4 ms when HZ is 250. Before high-resolution timers, sleeps and timeouts could be no finer than a jiffy.
monotonic clock
A clock that nothing can set, such as CLOCK_MONOTONIC, counting from an unspecified point, the boot on Linux. It never jumps, which makes it the clock for durations and timeouts.
tickless kernel
A kernel that leaves out the periodic timer interrupt where it is not needed: on idle CPUs, and with CONFIG_NO_HZ_FULL on CPUs with one runnable task.
timer slack
The time by which Linux may delay a thread's timer wake-ups so that it can handle several at once and save power: 50 µs inherited from init unless changed, none for real-time threads.
wall clock
The clock of the time of day, CLOCK_REALTIME on Linux: seconds since the Unix epoch. It can be set, so two readings can be out of order and a duration measured on it can come out wrong.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.