【问题标题】:glibc time function implementationglibc时间函数实现
【发布时间】:2017-12-07 04:56:54
【问题描述】:

我正在尝试了解 glibc 中 time() 的实现:https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86/time.c.html#time

如果你展开宏(悬停在它们上面),你会得到:

time_t time (time_t *t)
{
  unsigned long int resultvar;
  long int __arg1 = (long int) (t);
  register long int _a1 asm (""rdi"") = __arg1;
  asm volatile ( ""syscall\n\t"" : ""=a"" (resultvar) : ""0"" (201) , ""r"" (_a1) : ""memory"", ""cc"", ""r11"", ""cx"");
  (long int) resultvar;
}

汇编命令显然必须返回当前时间,但我不明白如何。检查英特尔手动系统调用,我看到它是 clobbers r11 和 cx,其他 clobbers 是 gcc 的东西,到那里,我很好。

从我读到的 (https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html) “0” 表示某些东西被用作输入和输出,虽然我不明白“(201)” 的含义(我期待一个变量名,而不是一个数字)。有什么想法吗?

我不确定 _a1 是否需要有信息,我假设它可以是 NULL,所以汇编命令的唯一真正输入是“(201)”,但我不知道系统调用如何与201 让我可以访问内部时钟。

谢谢。

附: 额外的问题:根据我在英特尔手册上阅读的内容,我的印象是唯一可用的时钟预计将在 MoBo 上,而不是 CPU 硬件的一部分。我是不是误会了?

【问题讨论】:

  • 201 是系统调用号。系统调用由数字标识。参见例如this table_a1 不能是 NULL,因为那是您的输出指针(从 t 参数初始化)。
  • 其实可以是NULL,结果也是通过rax寄存器返回的(=a就是这个意思)。
  • 谢谢@Jester。我发现 201 是 sys_time,private.h 中的 sys_time 调用 time(),我发现的唯一其他时间实现调用 __gettimeofday,__gettimeofday 的实现通过调用 time() 填充 timespec 结构。我陷入了一个循环......你知道实际从硬件时钟读取时间的代码在哪里吗?
  • 没关系,这是 linux 内核时间():elixir.free-electrons.com/linux/v4.11.7/source/kernel/time/…

标签: c++ linux gcc assembly glibc


【解决方案1】:

关于您的 PS:所有当前的 Intel CPU 都有自己的时钟,可以使用 rdtsc 等指令读取。但是,它们是否可以用作实时时钟取决于执行环境的配置(例如,需要内核/管理程序支持,可能需要禁用某些省电状态等)。

【讨论】:

  • 谢谢,但 rdtsc 不是时钟(如“它给出小时”,一个“绝对时间”),它是一个刻度计数器,可用于测量时间的流逝(a “相对时间”),以及(非常)旧架构上不同频率的所有警告。我试图了解如何在当前 glibc 中获得“绝对时间”,结果将所有内容都推迟到了 linux 内核。
  • 是的,glibc 使用内核,但它也可以利用 timegettimeofdayclock_gettime 的 vDSO 实现,这避免了系统调用的需要(如果其中一个 CPU确实可以使用时钟)。
  • 我一直在寻找如何访问这些时钟。我追踪了计时器结构,我知道 ntp,但我找不到读取“绝对时钟”的代码(很可能是程序集)。老实说,我仍然不确定它是 CPU 时钟还是 MoBo 的。
【解决方案2】:

这个看起来很复杂的内联汇编只会导致编译器发出以下汇编指令:

mov     eax, 201
syscall

所以,整个time 函数就是:

time:
    mov     eax, 201
    syscall
    ret

立即数201(16进制的0xC9)被移入EAX寄存器,然后执行syscall指令。这条指令就像名字所暗示的那样:它进行系统调用。这基本上是您在 Linux 上调用平台 API 函数的方式。另请参阅System V AMD64 ABI 的 A.2 节(“AMD64 Linux 内核约定”)。

简而言之:

  • 系统调用ID号放入rax

    (在这种情况下,数字只有32位,因此汇编代码将其放入eax。高32位是implicitly zeroed,节省了mov指令大小的一些字节。)

  • 系统调用的参数(如果有)放在寄存器中:rdirsirdxr10r8r9

    (在这种情况下,对于系统调用#201,没有需要指定的参数,所以这些寄存器都没有被time函数初始化。)

  • 调用syscall 后,其结果包含在rax 中。通常,负值(-4095 到 -1)表示错误,对应于−errno

  • 对于系统调用,rcxr11 寄存器被视为volatile,这意味着它们的内容会被破坏。如果调用者关心这些值,则需要保留它们。所有其他寄存器的值都保存在系统调用中。

    (这就是为什么 clobbers 在扩展的内联 asm 语法中存在的原因。)

有一个可用的 64 位 Linux 系统调用参考 here(32 位 Linux 系统调用是 here)。可以看到201(0xC9)对应sys_time

sys_timeRDI 寄存器解释为time_t* 值。这段代码:

long int __arg1 = (long int) (t);
register long int _a1 asm ("rdi") = __arg1;

使函数的参数t 存储在RDI 寄存器中。不过,这实际上不会导致生成任何机器指令,因为 System V AMD64 调用约定已经在 RDI 中传递了函数的第一个参数,所以 t 已经 在 @ 987654354@.

sys_time 系统调用只是填充它在RDI 中找到的指针,这与time 函数的t 参数相同。它还在RAX 中返回其结果(错误代码),它始终用于在 System V AMD64 调用约定下的函数的返回值,因此那里也不需要机器指令。

也许更清楚:

# inputs:  RDI is a pointer to time_t that will be filled in
# returns: result is left in RAX
time:
    mov     eax, 201
    syscall
    ret

【讨论】:

    猜你喜欢
    • 2013-09-09
    • 2020-09-17
    • 1970-01-01
    • 1970-01-01
    • 2020-12-15
    • 2021-03-24
    • 2013-05-25
    • 2011-06-10
    相关资源
    最近更新 更多