The C11
standard provides the function timespec_get
. If I run the example code on cppreference, or on my computer, it works:
#include <stdio.h> #include <time.h> int main(void) { struct timespec ts; timespec_get(&ts, TIME_UTC); char buff[100]; strftime(buff, sizeof buff, "%D %T", gmtime(&ts.tv_sec)); printf("Current time: %s.%09ld UTCn", buff, ts.tv_nsec); }
However, if I look at the sources of glibc here, the code is the following:
#include <time.h> /* Set TS to calendar time based in time base BASE. */ int timespec_get (struct timespec *ts, int base) { switch (base) { case TIME_UTC: /* Not supported. */ return 0; default: return 0; } return base; } stub_warning (timespec_get)
Which… should not work…
Which leads to the question: where is the source code of timespec_get
that is actually called?
Advertisement
Answer
The timespec_get
function’s implementation depends on the system the library is running on, so it appears both as a stub in time/timespec_get.c
(in case no implementation is available) and as various system-dependent implementations elsewhere.
You can see the Linux implementation in sysdeps/unix/sysv/linux/timespec_get.c
,
/* Set TS to calendar time based in time base BASE. */ int timespec_get (struct timespec *ts, int base) { switch (base) { int res; INTERNAL_SYSCALL_DECL (err); case TIME_UTC: res = INTERNAL_VSYSCALL (clock_gettime, err, 2, CLOCK_REALTIME, ts); if (INTERNAL_SYSCALL_ERROR_P (res, err)) return 0; break; default: return 0; } return base; }
This is is just a thin wrapper around a vDSO call, and the vDSO is part of the Linux kernel itself. If you are curious, look for the definition of clock_gettime
there. It’s unusual that clock_gettime
is in the vDSO, only a small number of syscalls are implemented this way.
Here is the x86 implementation for CLOCK_REALTIME
, found in arch/x86/entry/vdso/vclock_gettime.c
:
/* Code size doesn't matter (vdso is 4k anyway) and this is faster. */ notrace static int __always_inline do_realtime(struct timespec *ts) { unsigned long seq; u64 ns; int mode; do { seq = gtod_read_begin(gtod); mode = gtod->vclock_mode; ts->tv_sec = gtod->wall_time_sec; ns = gtod->wall_time_snsec; ns += vgetsns(&mode); ns >>= gtod->shift; } while (unlikely(gtod_read_retry(gtod, seq))); ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns); ts->tv_nsec = ns; return mode; }
Basically, there is some memory in your process which is updated by the kernel, and some registers in your CPU which track the passage of time (or something provided by your hypervisor). The memory in your process is used to translate the value of these CPU registers into the wall clock time. You have to read these in a loop because they can change while you are reading them… the loop logic detects the case when you get a bad read, and tries again.