Time Management in System Programming
Fundamental Concepts of Time
The kernel measures the passage of time using three distinct metrics:
- Wall time (real time): The actual time and date in the real world. Useful for interfacing with users and timestamping events.
- Process time: The time a process spends executing on a processor.
- User time: Time spent executing the process’s own code.
- System time: Time the kernel spends executing on behalf of the process.
- Monotonic time: A strictly linearly increasing time source, such as system uptime. Immune to user modification or clock skew adjustments. Optimal for calculating relative time differences.
Time representations fall into two formats:
- Relative time: A value measured relative to a benchmark, such as the current instant.
- Absolute time: A specific, fixed point in time. Represented in Unix systems as the number of elapsed seconds since the epoch (00:00:00 UTC on January 1, 1970).
The kernel tracks the progression of time via a software clock, managed by a periodic system timer.
- Tick (jiffy): The unit of time corresponding to a single timer interval.
- Jiffies counter: A 64-bit counter tracking elapsed ticks since boot.
- HZ: The frequency of the system timer, varying by architecture and kernel version (e.g., 100, 250, or 1000 hertz). Higher values provide greater resolution but incur increased timer overhead.
To manage and manipulate these fundamental time representations, the system defines specific data structures that provide varying levels of precision.
Time Data Structures
System interfaces utilize a hierarchy of data structures to represent time, moving from second-level precision to nanosecond-level precision.
time_t: An integer type (usuallylong) representing the number of elapsed seconds since the epoch.struct timeval: Provides microsecond precision.tv_sec: Seconds.tv_usec: Microseconds (suseconds_t).
struct timespec: Provides nanosecond precision, replacingtimevalin modern interfaces.tv_sec: Seconds.tv_nsec: Nanoseconds (long).
struct tm: Represents “broken-down” time for human-readable calendar date conversion.- Contains fields for seconds (
tm_sec), minutes (tm_min), hour (tm_hour), day of month (tm_mday), month (tm_mon), year since 1900 (tm_year), day of week (tm_wday), day in year (tm_yday), and daylight savings flag (tm_isdst).
- Contains fields for seconds (
clock_t: An integer type representing process time in clock ticks.
These precision data structures serve as the underlying types for the POSIX clock interfaces, which standardize discrete timekeeping sources.
POSIX Clocks
POSIX defines a standard for representing and querying system time sources, identified by the clockid_t type. Linux supports five primary clocks:
CLOCK_REALTIME: The system-wide real time (wall time) clock.CLOCK_MONOTONIC: A monotonically increasing clock representing elapsed time since system boot. Not settable by user processes.CLOCK_MONOTONIC_RAW: A Linux-specific monotonic clock ineligible for slewing (correction for clock skew).CLOCK_PROCESS_CPUTIME_ID: A high-resolution, per-process clock measuring execution time.CLOCK_THREAD_CPUTIME_ID: A high-resolution, per-thread execution clock.
The resolution of a specific POSIX clock is obtained using clock_getres():
- Returns the resolution in a
struct timespec. - Resolutions for
CLOCK_REALTIMEandCLOCK_MONOTONICtie directly to the frequency of the system timer. - Resolutions for
CLOCK_PROCESS_CPUTIME_IDrely on hardware registers (e.g., the TSC on x86) and often provide true nanosecond precision.
POSIX clocks form the foundation for accurately retrieving the current time at the required level of precision.
Retrieving Time
Interfaces for retrieving the current time correspond directly to the data structures introduced earlier, offering increasing degrees of precision.
time(): Returns the current absolute time as atime_t(seconds since the epoch).gettimeofday(): Returns the current absolute time in astruct timeval(microsecond resolution). The timezone parameter is obsolete and should be passed asNULL.clock_gettime(): Returns the current time of a specifiedclockid_tsource in astruct timespec(nanosecond resolution).times(): Retrieves process time for the invoking process and its waited-upon children, populating astruct tms.- Separates values into user time (
tms_utime), system time (tms_stime), child user time (tms_cutime), and child system time (tms_cstime).
- Separates values into user time (
Conversely, privileged processes use a corresponding set of interfaces to modify the system time.
Setting the System Time
Setting the system time requires the CAP_SYS_TIME capability (typically held by root). The interfaces mirror the retrieval functions:
stime(): Sets the system time using atime_tvalue.settimeofday(): Sets the system time using astruct timevalvalue.clock_settime(): Sets the system time for a specifiedclockid_tsource using astruct timespec. OnlyCLOCK_REALTIMEis generally settable.
Raw time values retrieved or set by these functions often require conversion to human-readable strings or calendar components for application logic.
Time Conversion and Manipulation
The C library provides a family of functions to translate between time_t epoch seconds and struct tm broken-down time, as well as ASCII strings.
gmtime()/gmtime_r(): Convertstime_tto astruct tmexpressed in UTC.localtime()/localtime_r(): Convertstime_tto astruct tmexpressed in the user’s local time zone.mktime(): Converts a localstruct tminto atime_tepoch value.asctime()/asctime_r(): Converts astruct tminto an ASCII string.ctime()/ctime_r(): Converts atime_tdirectly into an ASCII string.difftime(): Calculates the difference in seconds between twotime_tvalues, returning adouble.- Thread Safety Constraint: Functions lacking the
_rsuffix utilize statically allocated memory and are inherently thread-unsafe. Multithreaded programs must use the_rvariants.
While absolute time can be set directly via the earlier interfaces, maintaining system clock synchronization without disrupting time-dependent applications requires gradual adjustments.
Tuning the System Clock
Abrupt jumps in wall clock time negatively impact applications tracking absolute time (e.g., build systems like make evaluating file modification timestamps). The system combats clock skew dynamically without disruptive jumps.
- Slewing: The gradual adjustment of the system clock to converge on a corrected time, ensuring the clock remains monotonically increasing.
adjtime(): Adjusts the system time gradually by a delta provided in astruct timeval.- Positive delta: Kernel speeds up the system clock.
- Negative delta: Kernel slows down the system clock.
adjtimex(): Implements the complex RFC 1305 clock-adjustment algorithm using astruct timex. Modifies phase-locked loop (PLL) parameters, frequencies, and tolerances.
Beyond managing the underlying system clock, applications frequently rely on time tracking to suspend execution or trigger future events.
Sleeping and Waiting
Processes utilize sleep interfaces to suspend execution for a targeted delay. Modern interfaces utilize the timespec structure and POSIX clocks to achieve nanosecond precision and prevent race conditions.
sleep(): Suspends execution for a requested number of seconds.usleep(): Suspends execution for a requested number of microseconds. (Deprecated in modern POSIX).nanosleep(): Suspends execution with nanosecond resolution using astruct timespec.- Resilient to signal interruptions: Returns the remaining unslept time in an output
struct timespecparameter, allowing the process to safely resume the sleep.
- Resilient to signal interruptions: Returns the remaining unslept time in an output
clock_nanosleep(): Advanced sleep utilizingclockid_ttime sources.- Supports relative sleep (default) or absolute sleep via the
TIMER_ABSTIMEflag. - Absolute Sleep advantage: Eliminates the race condition between calculating a relative delay and initiating the sleep. The process wakes precisely when the target clock reaches the absolute timestamp.
- Supports relative sleep (default) or absolute sleep via the
- Timer Overruns: Occur when the timer granularity is coarser than the requested sleep interval. A timer with period produces an average overrun of .
When simple execution suspension is insufficient, asynchronous timer notifications provide precise scheduling for background events.
Timers
Timers notify a process when a specific delay has elapsed, dispatching an event asynchronously (typically a signal).
alarm(): The simplest timer. Schedules aSIGALRMsignal after a specified number of seconds.- Interval Timers: Armed via
setitimer()and queried viagetitimer(). Utilizestruct itimervalto specify both an initial expiration (it_value) and a repeating interval (it_interval). Interval timers operate in three modes:ITIMER_REAL: Measures real time. DispatchesSIGALRM.ITIMER_VIRTUAL: Decrements only while process user-space code executes. DispatchesSIGVTALRM.ITIMER_PROF: Decrements during both user-space and kernel-space execution. DispatchesSIGPROF.
- POSIX Advanced Timers: Deconstructs timer operations into creation, initialization, and deletion, utilizing
clockid_tsources.timer_create(): Instantiates a timer. Configures the expiration notification via astruct sigevent.SIGEV_NONE: Silent expiration.SIGEV_SIGNAL: Dispatches a specific signal, allowing custom payload integration.SIGEV_THREAD: Spawns a new POSIX thread to execute a provided callback function.
timer_settime(): Arms the timer using astruct itimerspec. Supports absolute expirations viaTIMER_ABSTIME.timer_gettime(): Retrieves the remaining time until expiration.timer_getoverrun(): Retrieves the count of timer expirations that occurred between the initial expiration and the process notification, mitigating issues caused by scheduling delays or signal masking.timer_delete(): Destroys the timer and frees kernel resources.