Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Layer 0 spec — time family

Technical specification — Version 1.0

Family overview

The air-sys-syscall::time module exposes time-management primitives: system clocks, timer FDs, precise sleeps. It is a small family (~8 syscalls) but essential for all patterns that need to measure or wait for durations.

Cross-cutting characteristics of the family.

  1. Multiple Linux clocks. The kernel exposes several clocks with distinct semantics: CLOCK_REALTIME (wall-clock time, adjustable), CLOCK_MONOTONIC (monotonic since boot), CLOCK_BOOTTIME (includes suspend), and others. The Air wrapper exposes them as a typed enum to prevent confusion between incompatible clocks.

  2. timerfd integrates with io_uring. Like signalfd, timerfd is an FD that becomes readable when the timer expires. This integrates naturally with io_uring.

  3. Nanosecond precision. The kernel’s Timespec structures express time with nanosecond resolution.

  4. EINTR handling. clock_nanosleep and blocking reads on timerfd can return EINTR. In accordance with convention 2 of ADR-021, it is propagated to the caller.

  5. Dual representations. Air exposes both Duration (Rust standard) for durations and Instant (Air custom) for points in time.

Subsection 1: Clocks and instants

Clock type

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum Clock {
    Realtime = 0,
    Monotonic = 1,
    ProcessCpuTime = 2,
    ThreadCpuTime = 3,
    MonotonicRaw = 4,
    RealtimeCoarse = 5,
    MonotonicCoarse = 6,
    Boottime = 7,
    RealtimeAlarm = 8,
    BoottimeAlarm = 9,
    Tai = 11,
}

impl Clock {
    pub const fn as_raw(self) -> i32;
    pub fn try_from_raw(value: i32) -> Option<Self>;
    pub fn name(self) -> &'static str;
}
}

Clock semantics.

  • Realtime: wall-clock UTC time. Adjustable by NTP. Discontinuous.
  • Monotonic: counter since an arbitrary point. Strictly increasing. For measuring durations.
  • ProcessCpuTime: CPU time consumed by the process.
  • ThreadCpuTime: CPU time consumed by the thread.
  • MonotonicRaw: like Monotonic but not affected by NTP slewing.
  • RealtimeCoarse, MonotonicCoarse: reduced-resolution versions, faster.
  • Boottime: like Monotonic but includes suspend.
  • RealtimeAlarm, BoottimeAlarm: can wake the system from suspend.
  • Tai: International Atomic Time.

Usage recommendations.

  • Measure a duration: Monotonic.
  • Timer or timeout: Monotonic or Boottime (if suspend-aware).
  • User timestamp: Realtime.
  • Wake the machine: BoottimeAlarm (with privileges).

clock_gettime

#![allow(unused)]
fn main() {
pub fn clock_gettime(clock: Clock) -> Result<Instant, Errno>;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant {
    clock: Clock,
    seconds: i64,
    nanoseconds: u32,
}

impl Instant {
    pub fn clock(&self) -> Clock;
    pub fn seconds(&self) -> i64;
    pub fn nanoseconds(&self) -> u32;
    pub fn as_duration_since_epoch(&self) -> Duration;
    
    pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration>;
    pub fn saturating_duration_since(&self, earlier: Instant) -> Duration;
}
}

Behavior.

Reads the current time for the specified clock. Returns an Instant that includes the clock used.

No cross-clock Instant arithmetic.

The type prevents, by construction, operations between Instant values of different clocks.

Performance. ~30-100 ns via vDSO for the main clocks.

clock_settime and clock_getres

#![allow(unused)]
fn main() {
pub fn clock_settime(clock: Clock, instant: Instant) -> Result<(), Errno>;
pub fn clock_getres(clock: Clock) -> Result<Duration, Errno>;
}

clock_settime requires CAP_SYS_TIME. Rarely used.

clock_getres returns the clock’s resolution.

Subsection 2: Sleep

clock_nanosleep

#![allow(unused)]
fn main() {
pub fn clock_nanosleep(
    clock: Clock,
    deadline: SleepDeadline,
) -> Result<(), SleepError>;

pub enum SleepDeadline {
    Relative(Duration),
    AbsoluteInstant(Instant),
}

pub enum SleepError {
    Interrupted { remaining: Duration },
    Other(Errno),
}
}

Behavior.

Blocks the thread until the specified instant (absolute mode) or for the specified duration (relative mode). Absolute mode is generally preferred to avoid drift.

Special EINTR handling.

On EINTR in relative mode, the kernel returns the time remaining to sleep. The Air wrapper retrieves it and passes it via SleepError::Interrupted { remaining }.

Subsection 3: timerfd

timerfd_create

#![allow(unused)]
fn main() {
pub fn timerfd_create(
    clock: Clock,
    flags: TimerFdFlags,
) -> Result<TimerFd, Errno>;

pub struct TimerFd { /* owns an internal OwnedFd */ }

impl TimerFd {
    pub fn as_fd(&self) -> BorrowedFd<'_>;
    pub fn into_fd(self) -> OwnedFd;
    
    pub fn arm(&self, spec: &TimerFdSpecification, flags: TimerSetFlags) -> Result<TimerFdSpecification, Errno>;
    pub fn disarm(&self) -> Result<TimerFdSpecification, Errno>;
    pub fn current(&self) -> Result<TimerFdSpecification, Errno>;
    
    pub fn read(&self) -> Result<u64, Errno>;
}

bitflags! {
    pub struct TimerFdFlags: i32 {
        const NONBLOCK = 0x800;
        const CLOEXEC = 0x80000;
    }
}

bitflags! {
    pub struct TimerSetFlags: i32 {
        const ABSTIME = 1;
        const CANCEL_ON_SET = 2;
    }
}

#[derive(Debug, Clone, Copy)]
pub struct TimerFdSpecification {
    pub initial: Duration,
    pub interval: Duration,
}
}

Underlying syscall. timerfd_create (x86_64 #283, ARM64 #85).

Behavior.

Creates a timerfd FD. The application arms it via arm() with a specification.

On each expiration, the FD becomes readable. read() returns the number of expirations that have occurred.

Spec modes.

  • One-shot: initial = duration, interval = ZERO.
  • Periodic: initial = duration, interval = period.
  • Disarmed: initial = ZERO, interval = ZERO.

ABSTIME and CANCEL_ON_SET.

ABSTIME: initial is an absolute instant.

CANCEL_ON_SET: if the Realtime clock is adjusted, the timer is cancelled.

Usage pattern.

#![allow(unused)]
fn main() {
let tfd = timerfd_create(Clock::Monotonic, TimerFdFlags::empty())?;
let spec = TimerFdSpecification {
    initial: Duration::from_secs(5),
    interval: Duration::ZERO,
};
tfd.arm(&spec, TimerSetFlags::empty())?;

// Wait for expiration
let expirations = tfd.read()?;

// Or via io_uring
ring.submit_read(tfd.as_fd(), buf, 0)?;
}

time family summary

Exposed functions:

CategoryMain functions
Clocksclock_gettime, clock_settime, clock_getres
Sleepclock_nanosleep
timerfdtimerfd_create, TimerFd::arm, TimerFd::disarm, TimerFd::current, TimerFd::read

Total: ~8 main public functions.

Non-wrapped syscalls (listed in UNSUPPORTED.md):

  • getitimer, setitimer (legacy)
  • times (covered by clock_gettime CpuTime)
  • time, gettimeofday (legacy)
  • adjtimex, clock_adjtime (deferred to phase 0)
  • nanosleep (replaced by clock_nanosleep)

Types added to air-sys-types

  • Clock
  • Instant
  • Timespec (internal)
  • TimerFd, TimerFdSpecification, TimerFdFlags, TimerSetFlags
  • SleepDeadline, SleepError

That is ~9 additional types.

Underlying decisions that emerged in the time family

1. Instant typed by clock.

Prevents, by construction, clock-mixing bugs.

2. SleepError distinct from Errno.

Dedicated error type to expose the “remaining” information for the EINTR case.

3. No direct std::time::Instant API.

The Air Instant type is distinct from std::time::Instant. Conversion is provided but not implicit.

4. TimerFd::read() returns the number of expirations.

Important for real-time patterns.


Document license: MPL 2.0 Status: Technical specification of the air-sys-syscall::time module (layer 0).