Linux时间子系统1:gettimeofday和clock_gettime实现分析

1. Linux用户态获取时间的函数

a. 秒级别的时间函数:time和stime

time和stime函数的定义如下:

 
  1. #include <time.h>

  2. time_t time(time_t *t);

  3. int stime(time_t *t);

        time函数返回了当前时间点到linux epoch的秒数(内核中timekeeper模块保存了这个值,timekeeper->xtime_sec)。stime是设定当前时间点到linux epoch的秒数。对于linux kernel,设定时间的进程必须拥有CAP_SYS_TIME的权利,否则会失败。

b. 微秒级别的时间函数:gettimeofday和settimeofday
 
  1. #include <sys/time.h>

  2. int gettimeofday(struct timeval *tv, struct timezone *tz);

  3. int settimeofday(const struct timeval *tv, const struct timezone *tz);

        这两个函数和上一小节秒数的函数类似,只不过时间精度可以达到微秒级别。gettimeofday函数可以获取从linux epoch到当前时间点的秒数以及微秒数

        显然,sys_gettimeofday和sys_settimeofday这两个系统调用是用来支持上面两个函数功能的,值得一提的是:这些系统调用在新的POSIX标准中 gettimeofday和settimeofday接口函数被标注为obsolescent,取而代之的是clock_gettime和clock_settime接口函数

        实际上上面的说法并不完全准确,在《Linux多线程服务端编程》一书5.1节中提到过,在x86-64的Linux上,gettimeofday不是系统调用,不会陷入内核。这种说法也有问题,因为gettimeofday确实是个系统调用,但是linux的vdso(virtual dynamic shared object)机制帮我们做到了在调用这些系统调用时不陷入内核,从而提高了性能。我们后面分析代码时会看到。

c. 纳秒级别的时间函数:clock_gettime和clock_settime
 
  1. #include <time.h>

  2. int clock_getres(clockid_t clk_id, struct timespec *res);

  3. int clock_gettime(clockid_t clk_id, struct timespec *tp);

  4. int clock_settime(clockid_t clk_id, const struct timespec *tp);

        如果不是clk_id这个参数,clock_gettime和clock_settime基本上是不用解释的,其概念和gettimeofday和settimeofday接口函数是完全类似的,除了精度是纳秒。Linux 5.10 定义了如下的clkid

 
  1. /*

  2. * The IDs of the various system clocks (for POSIX.1b interval timers):

  3. */

  4. #define CLOCK_REALTIME 0

  5. #define CLOCK_MONOTONIC 1

  6. #define CLOCK_PROCESS_CPUTIME_ID 2

  7. #define CLOCK_THREAD_CPUTIME_ID 3

  8. #define CLOCK_MONOTONIC_RAW 4

  9. #define CLOCK_REALTIME_COARSE 5

  10. #define CLOCK_MONOTONIC_COARSE 6

  11. #define CLOCK_BOOTTIME 7

  12. #define CLOCK_REALTIME_ALARM 8

  13. #define CLOCK_BOOTTIME_ALARM 9

        但是以上ID并没有包含全部的clock类型,时钟类型,以及时间与时钟源的关系,我们后面再来分析

2. gettimeofday和clock_gettime的实现

a. gettimeofday的实现

        我们先看gettimeofday的实现,使用gettimeofday的示例代码如下:
 

 
  1. #include <sys/time.h>

  2. #include <stdio.h>

  3. #include <unistd.h>

  4. int main(int argc, char* argv[])

  5. {

  6. struct timeval tv_begin, tv_end;

  7. gettimeofday(&tv_begin, NULL);

  8. printf("start tv_sec %ld tv_usec %ld\n", tv_begin.tv_sec, tv_begin.tv_usec);

  9. usleep(1000);

  10. gettimeofday(&tv_end, NULL);

  11. printf("end tv_sec %ld tv_usec %ld\n", tv_end.tv_sec, tv_end.tv_usec);

  12. }

在Linux kernel中,kernel/time/time.c目录下有如下代码:

 
  1. SYSCALL_DEFINE2(gettimeofday, struct __kernel_old_timeval __user *, tv,

  2. struct timezone __user *, tz)

  3. {

  4. if (likely(tv != NULL)) {

  5. struct timespec64 ts;

  6. ktime_get_real_ts64(&ts);

  7. if (put_user(ts.tv_sec, &tv->tv_sec) ||

  8. put_user(ts.tv_nsec / 1000, &tv->tv_usec))

  9. return -EFAULT;

  10. }

  11. if (unlikely(tz != NULL)) {

  12. if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))

  13. return -EFAULT;

  14. }

  15. return 0;

  16. }

如果不看gettimeofday的C库实现,肯定会认为gettimeofday就是直接使用上面的系统调用,实际上我一开始就是这么认为的。我们去glibc/musl看一下,这个函数在musl 1.2.3中的定义如下

 
  1. int gettimeofday(struct timeval *restrict tv, void *restrict tz)

  2. {

  3. struct timespec ts;

  4. if (!tv) return 0;

  5. clock_gettime(CLOCK_REALTIME, &ts);

  6. tv->tv_sec = ts.tv_sec;

  7. tv->tv_usec = (int)ts.tv_nsec / 1000;

  8. return 0;

  9. }

gettimeofday并没有直接使用系统调用,而是调用了clock_gettime,并且clockid直接填写了CLOCK_REALTIME,那么接下来我们就要分析clock_gettime函数了。

b. clock_gettime的实现

再看musl 1.2.3中的clock_gettime的代码

 
  1. int __clock_gettime(clockid_t clk, struct timespec *ts)

  2. {

  3. int r;

  4. #ifdef VDSO_CGT_SYM

  5. int (*f)(clockid_t, struct timespec *) =

  6. (int (*)(clockid_t, struct timespec *))vdso_func;

  7. if (f) {

  8. r = f(clk, ts);

  9. if (!r) return r;

  10. if (r == -EINVAL) return __syscall_ret(r);

  11. /* Fall through on errors other than EINVAL. Some buggy

  12. * vdso implementations return ENOSYS for clocks they

  13. * can't handle, rather than making the syscall. This

  14. * also handles the case where cgt_init fails to find

  15. * a vdso function to use. */

  16. }

  17. #endif

  18. #ifdef SYS_clock_gettime64

  19. r = -ENOSYS;

  20. if (sizeof(time_t) > 4)

  21. r = __syscall(SYS_clock_gettime64, clk, ts);

  22. if (SYS_clock_gettime == SYS_clock_gettime64 || r!=-ENOSYS)

  23. return __syscall_ret(r);

  24. long ts32[2];

  25. r = __syscall(SYS_clock_gettime, clk, ts32);

  26. if (r==-ENOSYS && clk==CLOCK_REALTIME) {

  27. r = __syscall(SYS_gettimeofday, ts32, 0);

  28. ts32[1] *= 1000;

  29. }

  30. if (!r) {

  31. ts->tv_sec = ts32[0];

  32. ts->tv_nsec = ts32[1];

  33. return r;

  34. }

  35. return __syscall_ret(r);

  36. #else

  37. r = __syscall(SYS_clock_gettime, clk, ts);

  38. if (r == -ENOSYS) {

  39. if (clk == CLOCK_REALTIME) {

  40. __syscall(SYS_gettimeofday, ts, 0);

  41. ts->tv_nsec = (int)ts->tv_nsec * 1000;

  42. return 0;

  43. }

  44. r = -EINVAL;

  45. }

  46. return __syscall_ret(r);

  47. #endif

  48. }

  49. weak_alias(__clock_gettime, clock_gettime);

很明显有2个分支,我们先看第一个分支,包含宏定义VDSO_CGT_SYM,这里不详细介绍vdso了,放在后面单独讲,vdso简而言之就是为了避免系统调用的开销,使用内存映射的办法,将内核数据映射到用户空间。

那么数据是如何更新到vdso数据的呢?在内核的时间更新函数timekeeping_update函数中调用update_vsyscall更新了vdso数据结构

那么clock_gettime是否在所有情况下都能从用户态获取到时间呢,其实并不是,即使在使能了vdso的情况下,也还是有一些场景需要trap进内核,比如访问phc clock的时间。所以内核还是支持正常的系统调用,内核实现如下:

 
  1. SYSCALL_DEFINE2(clock_gettime, const clockid_t, which_clock,

  2. struct __kernel_timespec __user *, tp)

  3. {

  4. const struct k_clock *kc = clockid_to_kclock(which_clock);

  5. struct timespec64 kernel_tp;

  6. int error;

  7. if (!kc)

  8. return -EINVAL;

  9. error = kc->clock_get_timespec(which_clock, &kernel_tp);

  10. if (!error && put_timespec64(&kernel_tp, tp))

  11. error = -EFAULT;

  12. return error;

  13. }

同样,我们可以看到根据clockid的不同可以获取到不同的时间,如下:

 
  1. static const struct k_clock * const posix_clocks[] = {

  2. [CLOCK_REALTIME] = &clock_realtime,

  3. [CLOCK_MONOTONIC] = &clock_monotonic,

  4. [CLOCK_PROCESS_CPUTIME_ID] = &clock_process,

  5. [CLOCK_THREAD_CPUTIME_ID] = &clock_thread,

  6. [CLOCK_MONOTONIC_RAW] = &clock_monotonic_raw,

  7. [CLOCK_REALTIME_COARSE] = &clock_realtime_coarse,

  8. [CLOCK_MONOTONIC_COARSE] = &clock_monotonic_coarse,

  9. [CLOCK_BOOTTIME] = &clock_boottime,

  10. [CLOCK_REALTIME_ALARM] = &alarm_clock,

  11. [CLOCK_BOOTTIME_ALARM] = &alarm_clock,

  12. [CLOCK_TAI] = &clock_tai,

  13. };

这里的时间的含义是什么,我们获取到的是什么时间,这个问题下面再讨论。

3. 遗留问题

a. vdso的机制:vdso是如何让用户态不必陷入到内核获取到时间的?

b. clock_gettime能够获取到的各类时间有什么不同?

这两个问题可参考下一篇:Linux时间子系统2: clock_gettime的VDSO机制分析-优快云博客

Linux时间子系统1:gettimeofday和clock_gettime实现分析_linux gettimeofday-优快云博客

<think>好的,我现在需要回答用户关于Linux平台上高精度时间函数的问题。首先,用户希望了解有哪些高精度的时间函数,以及它们的用法适用场景。我需要结合用户提供的三个引用内容,特别是引用[2]引用[3],因为它们提到了Linux的时钟系统内核文档。 首先,我应该回忆一下Linux中常用的时间函数。常见的如gettimeofday,但它的精度可能只有微秒级。更高精度的可能需要clock_gettime函数,它支持不同的时钟类型,比如CLOCK_REALTIMECLOCK_MONOTONIC,还有CLOCK_MONOTONIC_RAW以及CLOCK_BOOTTIME等。另外,可能需要提到POSIX标准中的这些函数,以及Linux特有的特性。 然后,根据引用[2],里面提到了clocksourcetimekeeper,这些属于内核中的时间子系统。用户可能需要知道,高精度时间函数在内核中的实现依赖于这些机制。比如,clocksource提供不同精度的计时源,而timekeeper负责管理时间。因此,clock_gettime的精度取决于底层clocksource的选择,比如TSC、HPET等,这些硬件提供的精度更高。 引用[3]提到Linux内核文档,可能需要参考内核文档中关于时间管理的部分,确认clock_gettime的细节,比如支持的时钟类型精度。同时,用户可能需要知道如何选择适合的时钟类型,比如CLOCK_REALTIME是系统实时时间,但可能受NTP调整影响,而CLOCK_MONOTONIC是单调递增的,适合测量时间间隔。 另外,可能需要提到高精度定时器相关的函数,比如timerfd_create,它可以与epoll结合使用,提供微秒级的定时精度。还有clock_getres函数,用来获取时钟的实际精度。 用户的问题可能还涉及到如何编写代码来获取高精度时间,所以需要给出示例代码,比如使用clock_gettime,并展示如何计算时间差。同时,需要注意不同时钟源的特性适用场景,比如实时应用、性能测试等。 需要检查是否有其他相关函数,比如rdtsc指令读取时间戳计数器,但这是平台相关的,可能不够便携。或者新的API如clock_gettime的扩展,比如支持纳秒级的精度。 总结回答的结构应该是:先介绍主要的函数(clock_gettime),说明支持的时钟类型,各自的优缺点,示例代码,以及如何选择适合的时钟类型。然后可能提到其他相关函数如timerfd_create,最后给出注意事项,如权限、硬件支持等。 另外,用户提供的引用[1]提到定时器的精度性能,所以在回答中需要强调不同函数的精度差异以及对系统资源的影响,比如某些高精度函数可能消耗更多资源。 最后,生成相关问题,比如如何选择时钟类型、如何优化定时器性能、与硬件时间源的关系等。</think>在Linux平台上实现高精度时间测量,主要依赖以下核心函数机制: ### 一、核心时间函数 1. **clock_gettime()** 支持多种时钟源,精度可达纳秒级: ```c #include <time.h> int clock_gettime(clockid_t clk_id, struct timespec *tp); ``` 常用时钟类型: - `CLOCK_REALTIME`:系统实时时间(受NTP调整影响)[^2] - `CLOCK_MONOTONIC`:单调递增时钟(忽略时间跳变) - `CLOCK_MONOTONIC_RAW`:原始硬件单调时钟(不受NTP影响) - `CLOCK_BOOTTIME`:包含系统休眠时间的单调时钟 2. **timerfd_create()** 结合epoll可实现微秒级定时: ```c #include <sys/timerfd.h> int timerfd_create(int clockid, int flags); ``` ### 二、精度层级对比 | 函数/机制 | 典型精度 | 适用场景 | |---------------------|----------------|--------------------------| | gettimeofday() | 1微秒 | 传统时间戳 | | clock_gettime() | 1纳秒 | 高精度测量/同步 | | TSC寄存器 | CPU周期级 | 内核内部计时 | | RTC硬件时钟 | 秒级 | 系统启动基础时钟[^2] | ### 三、示例代码(纳秒级时间差计算) ```c #include <stdio.h> #include <time.h> int main() { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); // 被测代码 clock_gettime(CLOCK_MONOTONIC, &end); long diff_ns = (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); printf("耗时: %ld 纳秒\n", diff_ns); return 0; } ``` ### 四、关键注意事项 1. 硬件依赖性:实际精度受CPU的TSC、HPET等时钟源影响 2. 权限要求:部分时钟源需要`CAP_SYS_TIME`权限 3. 性能损耗:高频调用(>10K次/秒)可能影响系统性能[^1] 4. 时钟漂移:建议配合NTP/PTP进行时间同步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值