glibc时间函数实现

glibc time function implementation

我想了解 time() 在 glibc 中的实现: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;
}

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

根据我的阅读 (https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html) “0” 表示某物同时用作输入和输出,尽管我不明白“(201)” 的意思(我期待的是一个变量名,而不是一个号码)。有什么想法吗?

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

谢谢。

P.S。 奖励问题:根据我在 Intel 手册上读到的内容,我的印象是唯一可用的时钟应该在 MoBo 上,而不是 CPU 硬件的一部分。我是不是误会了?

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

mov     eax, 201
syscall

所以,整个 time 函数就是:

time:
    mov     eax, 201
    syscall
    ret

将立即数201(十六进制表示为0xC9)移入EAX寄存器,然后执行syscall指令。这条指令的作用正如其名称所暗示的那样:它进行系统调用。这基本上就是您在 Linux 上调用平台 API 函数的方式。另请参阅 System V AMD64 ABI.

的 A.2 节(“AMD64 Linux 内核约定”)

简述:

  • 系统调用ID号放入rax.

    (在这种情况下,数字只有 32 位,因此汇编代码将其放入 eax。高 32 位为 implicitly zeroed,在 mov指令。)

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

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

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

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

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

有 64 位 Linux 可用系统调用的参考 here (32-bit Linux system calls are 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 已经是 RDI.

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

也许更清楚:

# 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

关于您的 PS:所有当前的英特尔 CPU 都有自己的时钟,可以使用 rdtsc 等指令读取。然而,它们是否可以用作实时时钟取决于执行环境的配置(例如,需要kernel/hypervisor支持,可能需要禁用某些省电状态等)。