1. Introduction
Time is a fundamental aspect of computing, and accurate timekeeping is crucial for various applications on Linux systems. In this tutorial, we’ll learn about timekeeping in Linux.
First, we’ll delve into the two essential clocks provided by the clock_gettime() function in Linux: CLOCK_REALTIME and CLOCK_MONOTONIC.
The clock_gettime() function is a POSIX standard function, so CLOCK_REALTIME and CLOCK_MONOTONIC are available on most Unix-like operating systems. We’ll understand their behavior through code examples and highlight their unique characteristics and practical applications.
Finally, we’ll briefly discuss other Linux clocks and some special considerations to enable us to make informed decisions for accurate and reliable time measurements. Let’s get started!
2. CLOCK_REALTIME
CLOCK_REALTIME is a well-known clock representing the current wall-clock time, commonly called time-of-day. This clock is akin to the standard time we’re familiar with in our daily lives when we look at our watches or check the system clock.
Additionally, CLOCK_REALTIME is a clock source used in computer systems to keep track of the actual time in seconds and nanoseconds since the Epoch (January 1, 1970).
It’s unaffected by any adjustments like time zone changes or leap seconds. As a result, it provides a continuous and linear representation of time, making it suitable for general timekeeping purposes and synchronization across various applications and processes.
2.1. Retrieving the Time Using CLOCK_REALTIME
To better understand CLOCK_REALTIME, let’s consider a C program to retrieve and display the current time using the CLOCK_REALTIME clock type.
To do this, we create a file with a .c extension and save it. For this example, we use realtime_example.c. Then, we proceed to write our program:
#include <stdio.h>
#include <time.h>
int main() {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("CLOCK_REALTIME: %ld seconds, %ld nanoseconds\n", ts.tv_sec, ts.tv_nsec);
return 0;
}
Here, we begin by including two standard C libraries – <stdio.h> for input/output operations and < time.h> for time-related functions. These libraries are necessary to access the clock_gettime() function and manipulate time data.
Next, the main() function serves as the entry point for the code execution. Within this function, we declare a variable ts of type struct timespec. This time structure stores time-related information, specifically, the seconds and nanoseconds components.
Then, we retrieve the current time by calling the clock_gettime() function. By receiving CLOCK_REALTIME as the first argument to clock_gettime(), the function captures the current system time, representing the wall clock – the time-of-day time we typically observe in our everyday lives. The obtained time is then stored in the ts variable for further processing.
After obtaining the time, the program utilizes the printf() function to present the time information in a human-readable format. The format string “CLOCK_REALTIME: %ld seconds, %ld nanoseconds\n” specifies how the time values should be printed.
Furthermore, the tv_nsec member stores the number of nanoseconds since the last second, while the tv_sec member stores the number of seconds since the Epoch (January 1, 1970). The %ld format specifier represents long integers suitable for the tv_sec and tv_nsec members of the struct timespec.
Finally, the program reaches its conclusion, and the main() function returns 0, indicating the successful execution of the program.
2.2. Testing the CLOCK_REALTIME Script
Now that we understand what our C program does, let’s go through a step-by-step guide to execute it. Let’s save this C program as realtime_example.c, if we haven’t already. Then, we open a terminal in the directory where we saved the file:
$ ls
Documents Downloads realtime_example.c
To compile our C code, we must install the GNU C compiler (GCC), the commonly used C code compiler in Linux, on our system by first updating the package lists for upgrades:
$ sudo apt update
Hit:1 http://ke.archive.ubuntu.com/ubuntu jammy InRelease
...
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Once we know all of our packages are up-to-date, then, we install GCC with apt:
$ sudo apt install gcc
Reading package lists... Done
Building dependency tree... Done
...
Done...
After installing the GCC compiler on our system, we use it to compile the code:
$ gcc -o realtime_example realtime_example.c
This command compiles the code and creates an executable named realtime_example. Now, we execute the compiled program:
$./realtime_example
CLOCK_REALTIME: 1689848356 seconds, 2326000 nanoseconds
As we can see, CLOCK_REALTIME returns the current wall-clock time, corresponding to the time of day. It includes both seconds and nanoseconds and can be influenced by Network Time Protocol (NTP) adjustments and other system time changes.
As a result, if we execute this CLOCK_REALTIME script at different times, we may observe different outputs. For instance, if an NTP daemon updates our system’s time while the program is running, the CLOCK_REALTIME value reflects that change, potentially leading to jumps in time.
In short, running this script displays the current time in seconds and nanoseconds since the Epoch. The C program utilizes the clock_gettime() function with the CLOCK_REALTIME clock type to retrieve the current wall-clock, time-of-day time. The obtained time is then printed to the console, allowing us to observe the familiar time representation we commonly use daily.
2.3. CLOCK_REALTIME and Timestamps
CLOCK_REALTIME is suitable for tasks that require timestamps representing the current wall-clock time. We commonly use it in logging and event tracking, where human-readable timestamps are crucial for understanding the sequence of events.
However, we should be aware of potential time leaps caused by NTP adjustments when using CLOCK_REALTIME for timestamping. If we need to maintain a chronological order of events, we should consider using CLOCK_MONOTONIC to ensure a linear and reliable representation of time.
3. CLOCK_MONOTONIC
CLOCK_MONOTONIC differs significantly from CLOCK_REALTIME, as it measures the absolute elapsed wall-clock time since an unspecified fixed point in the past.
The starting point for CLOCK_MONOTONIC is unspecified because it’s not always possible to determine precisely when the system was booted up. For example, in cases where we boot a system from a network boot image, the time the system boots up may not align with the clock’s starting point.
Additionally, we should know that CLOCK_MONOTONIC isn’t subject to adjustments, such as NTP updates or manual changes to the system time. In contrast, these adjustments can impact the time represented by CLOCK_REALTIME.
By remaining unaffected by such changes, CLOCK_MONOTONIC offers a consistent reference for measuring durations and intervals, providing a reliable timekeeping mechanism even when system time may change or be corrected.
3.1. Key Features
Despite its unspecific starting point, CLOCK_MONOTONIC’s accuracy remains untouched. The clock increments monotonically, ensuring that it’s always possible to measure the time that has passed since some arbitrary point in the past. This behavior is crucial for applications that require consistent and continuous time measurements.
Furthermore, the monotonic nature of CLOCK_MONOTONIC is particularly valuable because it guarantees that the clock never goes backward. While the exact starting point might not be known, the fact that the clock increments only in one direction is sufficient for most applications’ timekeeping needs.
Moreover, CLOCK_MONOTONIC is a non-settable clock, meaning neither the user nor the kernel can change its value. This characteristic ensures that the time represented by CLOCK_MONOTONIC remains consistent throughout the system’s uptime, providing a reliable and continuous time source.
Due to these qualities, CLOCK_MONOTONIC is well-suited for accurately measuring time intervals and calculating the elapsed time between two events. Its stable and linearly increasing time source makes it an excellent choice for tasks that demand precision and reliability in time measurements.
3.2. Retrieving the Time Using CLOCK_MONOTONIC
We’ll also write a C program to see CLOCK_MONOTONIC at work. Let’s create a new C file and save it. For this example, we use monotonic_example.c. Then, we can proceed to write our program:
#include <stdio.h>
#include <time.h>
int main() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("CLOCK_MONOTONIC: %ld seconds, %ld nanoseconds\n", ts.tv_sec, ts.tv_nsec);
return 0;
}
Similar to our previous interaction, we obtain the current time by calling the clock_gettime function and passing CLOCK_MONOTONIC as the first argument, representing the time since the unspecified fixed point. Since this point doesn’t change after system startup, CLOCK_MONOTONIC remains unaffected by adjustments made by NTP or any other system time modifications.
3.3. Testing the CLOCK_MONOTONIC Script
Now that we understand the program better, we save it, navigate to its directory, compile the program, and execute it:
$ gcc monotonic_example.c -o monotonic_example
$ ./monotonic_example
CLOCK_MONOTONIC: 1689848386 seconds, 860000 nanoseconds
In contrast to CLOCK_REALTIME, CLOCK_MONOTONIC provides the absolute elapsed time since an unspecified fixed point in the past. This clock remains stable and unaffected by external time adjustments, such as NTP or manual changes to the system time. Consequently, the CLOCK_MONOTONIC value always increases monotonically regardless of any system time changes.
To better understand these differences, we can run the code examples for both clocks at different times and observe their outputs. By comparing this output with that of CLOCK_REALTIME, we can observe how the CLOCK_REALTIME value may change if we execute the code at different times, while the CLOCK_MONOTONIC value only increases monotonically regardless of system time changes.
3.4. CLOCK_MONOTONIC and Suspend Time
An essential characteristic of CLOCK_MONOTONIC is that it continues to tick at a constant rate without any jumps. However, the behavior of CLOCK_MONOTONIC during system suspension is inconsistent across all Linux distributions. Some distributions, such as Ubuntu, don’t increment CLOCK_MONOTONIC during suspend, while others, such as Fedora, do.
The best way to determine how CLOCK_MONOTONIC behaves on our system concerning system suspension is to consult the documentation for our distribution. If we’re unsure, we can always use the CLOCK_BOOTTIME clock, which is guaranteed to keep running during system suspension, making it suitable for applications requiring accurate timekeeping.
3.5. Handling External Time Sources
When dealing with external time sources, such as timestamps from other systems or network devices, using CLOCK_MONOTONIC is the safer option. Since it remains unaffected by NTP or other time adjustments, CLOCK_MONOTONIC provides consistent time measurements across different machines or devices.
Choosing the appropriate clock for specific tasks ensures that our Linux applications handle time-related operations effectively and accurately.
3.6. Computing Elapsed Time
CLOCK_MONOTONIC is the go-to choice when calculating the elapsed time between two events or measuring the performance of specific operations in our code. Its monotonic nature ensures precise and consistent results regardless of external time adjustments.
For example, when profiling the execution time of a complex algorithm, we can use CLOCK_MONOTONIC to measure the time taken accurately, unaffected by any changes to the system’s wall-clock time.
4. Interaction With NTP Adjustments
The interaction between NTP adjustments and the clocks, CLOCK_REALTIME and CLOCK_MONOTONIC, is crucial to understanding how each clock behaves under the influence of time corrections. Let’s explore how NTP adjustments differently affect these clocks.
4.1. CLOCK_REALTIME, CLOCK_MONOTONIC and NTP Adjustments
CLOCK_REALTIME represents the best-guess for the current wall-clock, time-of-day time. NTP adjustments can cause the system time to be speeded up or slowed down by up to 0.05%. Consequently, when NTP changes the system time, CLOCK_REALTIME also adjusts accordingly. This means that CLOCK_REALTIME is susceptible to forward and backward jumps as NTP corrects the system time to keep it in sync with more accurate time references.
On the other hand, CLOCK_MONOTONIC represents the absolute elapsed wall-clock time since an arbitrary, fixed point in the past. Unlike CLOCK_REALTIME, NTP adjustments don’t have any effect on CLOCK_MONOTONIC.
Despite NTP corrections, CLOCK_MONOTONIC remains completely immune to changes in the system time-of-day clock. This characteristic ensures that CLOCK_MONOTONIC continues to advance steadily, unaffected by system time adjustments or NTP synchronization.
4.2. Further Illustration
To better understand the behavior of these clocks, let’s consider two examples. Suppose we have a CLOCK_REALTIME value of 1626750000, representing the time in seconds since the Epoch. After an NTP adjustment, the system time may change to 1626750020, causing CLOCK_REALTIME to also jump to this new value, reflecting the updated system time.
Now, let’s consider CLOCK_MONOTONIC. Suppose we have a CLOCK_MONOTONIC value of 100 seconds since the system’s arbitrary starting point. After an NTP adjustment that significantly changes the system time, CLOCK_MONOTONIC remains at 100 seconds. This example demonstrates that CLOCK_MONOTONIC doesn’t experience any jumps or changes due to NTP corrections, as it always maintains its steady advancement.
When choosing the appropriate clock for our application, we must consider the specific requirements and use cases. If we need accurate time intervals and consistent measurements, CLOCK_MONOTONIC is the recommended choice. If we require wall-clock time with the ability to handle system time changes, CLOCK_REALTIME might be more suitable.
5. Other POSIX-Compliant Clocks
When working with timekeeping on Linux, there are other POSIX-compliant clocks and special considerations that we should be aware of, especially regarding the behavior of CLOCK_REALTIME and CLOCK_MONOTONIC.
We can use these clocks for various purposes, such as measuring CPU time, tracking system boot time, and measuring intervals not affected by NTP adjustments.
Let’s briefly discuss these clocks and some special considerations.
5.1. CLOCK_BOOTTIME
As mentioned earlier, CLOCK_MONOTONIC may not measure time during system suspension on some Linux systems. In such cases, we can utilize CLOCK_BOOTTIME, a Linux-specific clock that continues to run even during system suspension.
Thus, CLOCK_BOOTTIME is useful for applications that require accurate timekeeping throughout the system’s uptime, including periods of suspension.
5.2. CLOCK_MONOTONIC_RAW
CLOCK_MONOTONIC_RAW is a newer clock available on some systems, and its primary advantage over CLOCK_MONOTONIC is its increased immunity to NTP adjustments. Like CLOCK_MONOTONIC, it remains unaffected by changes in the system’s wall-clock time due to NTP corrections. This means that even if NTP adjusts the system time, CLOCK_MONOTONIC_RAW provides a steady and reliable time reference.
However, the main distinction lies in their precision. While CLOCK_MONOTONIC provides a high level of accuracy, CLOCK_MONOTONIC_RAW might sacrifice some precision for greater immunity to NTP adjustments. As a result, CLOCK_MONOTONIC_RAW may have slightly coarser granularity compared to CLOCK_MONOTONIC, potentially leading to less precise time measurements.
For instance, if we need to measure the time taken to perform a task, we may consider CLOCK_MONOTONIC, as it’s more precise. However, to strictly ensure that NTP adjustments don’t affect our application, we may want to use CLOCK_MONOTONIC_RAW.
5.3. CLOCK_PROCESS_CPUTIME_ID
We can use CLOCK_PROCESS_CPUTIME_ID to measure the CPU time a process uses. This clock isn’t affected by NTP adjustments, so we can always use it to track the CPU time a process uses over time precisely.
For example, as system administrators, we can use CLOCK_PROCESS_CPUTIME_ID to track the CPU time that a long-running process uses. We can leverage this information to identify if the process uses too much CPU resources and take corrective action if necessary.
5.4. CLOCK_THREAD_CPUTIME_ID
CLOCK_THREAD_CPUTIME_ID is similar to CLOCK_PROCESS_CPUTIME_ID but measures the CPU time a thread uses. We can use this clock to track a thread’s CPU time over time or compare the CPU time different threads use.
For example, as developers, we can use CLOCK_THREAD_CPUTIME_ID to track the CPU time that a single thread uses in a multithreaded application. We can now leverage this information to identify if the thread uses too much CPU resources and optimize the application to improve performance.
6. vDSO and Performance Considerations
Linux’s Virtual Dynamic Shared Object (vDSO) mechanism accelerates certain clock functions, such as clock_gettime(), by allowing direct access to these functions from user space without making a system call. This optimization significantly improves performance when using clocks like CLOCK_MONOTONIC and CLOCK_REALTIME.
However, we must be aware that some clocks, such as CLOCK_MONOTONIC_RAW or other POSIX clocks we have discussed, may not be available in the vDSO, and using them could incur a system call overhead. The availability of clocks in the vDSO can vary based on the kernel version and architecture.
While most applications may not need to worry about these performance considerations, we should understand them if we work on time-critical applications or those requiring frequent clock operations.
Additionally, the vDSO can introduce latency spikes when the kernel updates shared memory areas with clock counters. This situation is more likely to occur with clocks accelerated by the vDSO, like CLOCK_MONOTONIC and CLOCK_REALTIME. We must consider this if our application requires ultra-low latency.
7. Conclusion
In this article, we delved into the fundamental clocks provided by the Linux clock_gettime() function – CLOCK_REALTIME and CLOCK_MONOTONIC. These clocks are vital in accurate timekeeping on Linux systems and cater to different application requirements.
In addition to these essential clocks, we explored other POSIX-compliant clocks, such as CLOCK_BOOTTIME, useful for accurate timekeeping during system suspension, and CLOCK_MONOTONIC_RAW, which offers greater immunity to NTP adjustments at the cost of some precision. Furthermore, we discussed special considerations, like the interaction of NTP adjustments with the clocks and the performance considerations introduced by the vDSO mechanism.
Finally, whether we need to compute elapsed time between events, maintain chronological order, or handle external time sources, understanding the strengths and limitations of each clock empowers us to make well-informed decisions and ultimately improve the overall functionality of our Linux-based software.