macOS 上的 gettimeofday() 是不是使用系统调用?

Posted

技术标签:

【中文标题】macOS 上的 gettimeofday() 是不是使用系统调用?【英文标题】:Does gettimeofday() on macOS use a system call?macOS 上的 gettimeofday() 是否使用系统调用? 【发布时间】:2017-04-19 10:52:17 【问题描述】:

我希望gettimeofday() 会调用系统调用来完成实际获取时间的工作。但是,运行以下程序

#include <stdlib.h>
#include <sys/time.h>
#include <stdio.h>

int main(int argc, char const *argv[])

    struct timeval tv;

    printf("Before gettimeofday() %ld!\n", tv.tv_sec);

    int rc = gettimeofday(&tv, NULL);

    printf("After gettimeofday() %ld\n", tv.tv_sec);

    if (rc == -1) 
        printf("Error: gettimeofday() failed\n");
        exit(1);
    

    printf("Exiting ! %ld\n", tv.tv_sec);
    return 0;

dtruss -d 下返回一长串系统调用,最后一个是:

RELATIVE SYSCALL(args)           = return

... lots of syscalls with earlier timestamps ...

    3866 fstat64(0x1, 0x7FFF56ABC8D8, 0x11)      = 0 0
    3868 ioctl(0x1, 0x4004667A, 0x7FFF56ABC91C)      = 0 0
    3882 write_nocancel(0x1, "Before gettimeofday() 0!\n\0", 0x19)       = 25 0
    3886 write_nocancel(0x1, "After gettimeofday() 1480913810\n\0", 0x20)        = 32 0
    3887 write_nocancel(0x1, "Exiting ! 1480913810\n\0", 0x15)       = 21 0

看起来gettimeofday() 没有使用系统调用,但这似乎是错误的——内核肯定负责系统时钟吗? dtruss 有什么遗漏吗?我是不是读错了输出?

【问题讨论】:

在 Solaris 上,gettimeofday() 只是读取内存位置。 macOS Sierra(或之前的 Mac OS X 版本)很可能发生了类似的事情。您可能有兴趣也可能没有兴趣注意到 macOS Sierra(但不是早期版本)终于支持clock_gettime() 可以在source code-#116中看到gettimeofday()系统调用 在 Linux 上,gettimeofday() 等系统调用可以实现为“普通函数调用和少量内存访问”:vDSO 【参考方案1】:

作为 TheDarkKnight pointed out,有一个 gettimeofday 系统调用。然而,用户空间gettimeofday 函数经常调用相应的系统调用,而是__commpage_gettimeofday,它试图从进程地址空间的一个特殊部分读取时间,称为commpage。仅当此调用失败时,gettimeofday 系统调用才会用作后备。这将大多数调用gettimeofday 的成本从普通系统调用降低到仅读取内存。

Mac OSX Internals: A Systems Approach 这本书描述了commpage。简而言之,它是内核内存的一个特殊区域,映射到每个进程地址空间的最后八页。除其他外,它包含“从内核异步更新并从用户空间原子读取,导致偶尔读取失败”的时间值。

为了查看gettimeofday() 系统调用被用户空间函数调用的频率,我编写了一个测试程序,在一个紧密的循环中调用了gettimeofday() 1 亿次:

#include <sys/time.h>
int main(int argc, char const *argv[])

    const int NUM_TRIALS = 100000000;
    struct timeval tv;
    for (int i = 0; i < NUM_TRIALS; i++) 
        gettimeofday(&tv, NULL);
    
    return 0;

在我的机器上在dtruss -d 下运行它表明这触发了对gettimeofday() 系统调用的10-20 次调用(占所有用户空间调用的0.00001%-0.00002%)。


对于那些感兴趣的人,source code 中用户空间 gettimeofday() 函数(适用于 macOS 10.11 - El Capitan)的相关行是

if (__commpage_gettimeofday(tp))       /* first try commpage */
    if (__gettimeofday(tp, NULL) < 0)  /* if it fails, use syscall */
        return (-1);
    

函数__commpage_gettimeofdaycombines 从commpage 读取时间戳并读取时间戳计数器寄存器以计算自纪元以来的时间(以秒和微秒为单位)。 (rdstc 指令在 _mach_absolute_time 内部。)

【讨论】:

非常有趣。谢谢。 好答案。您提到:这将大多数 gettimeofday 调用的成本从普通系统调用降低到仅读取内存。这似乎并不完全正确:___commpage_gettimeofday_mach_absolute_time 函数(前者调用后者)每条指令有几十条指令,它们之间可能有 10 次总内存读取,rdtsc 调用本身是两打或更多周期(加上它们有两条 lfence 指令在那里出于某种原因)。因此,您可能正在查看至少 50 个周期。仍然比内核调用好得多!【参考方案2】:

使用 dtrace 代替 dtruss 将消除您的疑虑。 gettimeofday() 本身就是一个系统调用。如果你运行 dtrace 脚本,你可以看到这个系统调用被调用。

您可以使用以下 dtrace 脚本 "dtrace1.d"

syscall:::entry
/ execname == "foo" / 


(foo 是您的可执行文件的名称)

要在 dtrace 上运行,请使用:dtrace -s dtrace1.d

然后执行你的程序来查看你的程序使用的所有系统调用

【讨论】:

我已按照您的指示进行操作,但 dtrace 无法识别 gettimeofday 系统调用。它的输出几乎与dtruss 相同,除了在程序的最后列出对exit() 的调用之外,最后五个系统调用(见问题)是相同的。我正在运行 El Capitan (10.11.6)。您在计算机上看到了什么不同的东西吗?

以上是关于macOS 上的 gettimeofday() 是不是使用系统调用?的主要内容,如果未能解决你的问题,请参考以下文章

gettimeofday()函数的使用方法

do_gettimeofday使用方法

使用 CPU 计数器与 gettimeofday?

linux gettimeofday()的微秒时间是怎么得到的,它的精度是多少?

使用 gettimeofday 钳制 FPS

Linux时间函数之gettimeofday()函数之使用方法