最快的Linux系统调用

时间:2018-02-21 18:34:06

标签: linux performance x86-64 microbenchmark

在x86-64英特尔系统上支持syscallsysret"最快"在vanilla内核上使用64位用户代码进行系统调用?

特别是,它必须是系统调用,才能运用syscall / sysret用户< - >内核转换 1 ,但除此之外的工作量最少。它甚至不需要进行系统调用本身:某种类型的早期错误,它永远不会调度到内核端的特定调用,这是好的,只要它不会因为这样而走慢路径。

此调用可用于估算原始syscallsysret开销,与呼叫完成的任何工作无关。

1 特别是,这排除了似乎是系统调用但在VDSO中实现的内容(例如,clock_gettime)或由运行时缓存(例如{{1 }})。

4 个答案:

答案 0 :(得分:7)

不存在的,因此很快就会返回-ENOSYS。

来自arch / x86 / entry / entry_64.S:

#if __SYSCALL_MASK == ~0
    cmpq    $__NR_syscall_max, %rax
#else
    andl    $__SYSCALL_MASK, %eax
    cmpl    $__NR_syscall_max, %eax
#endif
    ja  1f              /* return -ENOSYS (already in pt_regs->ax) */
    movq    %r10, %rcx

    /*
     * This call instruction is handled specially in stub_ptregs_64.
     * It might end up jumping to the slow path.  If it jumps, RAX
     * and all argument registers are clobbered.
     */
#ifdef CONFIG_RETPOLINE
    movq    sys_call_table(, %rax, 8), %rax
    call    __x86_indirect_thunk_rax
#else
    call    *sys_call_table(, %rax, 8)
#endif
.Lentry_SYSCALL_64_after_fastpath_call:

    movq    %rax, RAX(%rsp)
1:

答案 1 :(得分:3)

使用无效的系统调用号码,因此调度代码只返回
eax = -ENOSYS而不是分配到系统调用处理函数。

除非这导致内核使用iret慢速路径而不是sysret / sysexit。这可能解释the measurements显示无效数字比syscall(SYS_getpid)慢17个周期,因为glibc错误处理(设置errno)可能无法解释它。但是从我阅读内核源代码开始,我没有看到为什么在返回sysret时它仍然没有使用-ENOSYS的原因。

此答案适用于sysenter,而非syscall 。问题最初是sysenter / sysret(这很奇怪,因为sysexitsysenter一致,而sysretsyscall一致。我基于sysenter回答了x86-64内核上的32位进程。

在内核中更有效地处理本机64位syscall。 (更新;使用Meltdown / Spectre缓解补丁,它在4.16-rc2中仍为dispatches via C do_syscall_64

我的What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? Q& A概述了从compat模式到x86-64内核(entry_64_compat.S)的系统调用入口点的内核端。这个答案只是考虑了相关部分。

该答案中的链接是Linux 4.12的源代码,它们不包含Meltdown缓存页表操作,因此这将是重要的额外开销。

int 0x80sysenter有不同的入口点。您正在寻找entry_SYSENTER_compat。 AFAIK,sysenter总是去那里,即使你在64位用户空间进程中执行它。 Linux的入口点将常量__USER32_CS作为保存的CS值推送,因此它将始终以32位模式返回用户空间。

在推送寄存器以在内核堆栈上构造struct pt_regs之后,有TRACE_IRQS_OFF个钩子(不知道有多少指令),然后写出call do_fast_syscall_32在C.(本机64位syscall调度直接从asm完成,但32位compat系统调用总是通过C)调度。

do_syscall_32_irqs_on in arch/x86/entry/common.c非常轻量级:只检查进程是否被跟踪(我认为这是strace可以通过ptrace挂钩系统调用的方式),然后

   ...
    if (likely(nr < IA32_NR_syscalls)) {
        regs->ax = ia32_sys_call_table[nr]( ... arg );
    }

    syscall_return_slowpath(regs);
}

AFAIK,内核在此函数返回后可以使用sysexit

因此,无论EAX是否具有有效的系统调用号,返回路径都是相同的,并且显然在没有调度的情况下返回是通过该函数的最快路径,尤其是在具有Spectre缓解的内核中,其中表的间接分支函数指针会通过一个retpoline并且总是误预测。

如果你想在没有额外开销的情况下真正测试sysenter / sysexit,你需要修改Linux以便在不检查跟踪或推送/弹出所有寄存器的情况下放入更简单的入口点。

您可能还想修改ABI以在寄存器中传递返回地址(如syscall自己做的那样)而不是保存在Linux的用户空间堆栈中当前sysenter ABI确实如此;它必须get_user()才能读取它应返回的EIP值。

如果所有这些开销都是您要衡量的内容的一部分,那么您肯定都设置了一个为您提供-ENOSYS的eax;在最坏的情况下,如果根据正常的32位系统调用,分支预测器对于该分支是热的,那么你将从范围检查中获得一个额外的分支未命中。

答案 2 :(得分:1)

有些系统调用甚至不通过任何用户 - >内核转换,请阅读vdso(7)

我怀疑这些VDSO系统调用(例如time(2),...)是最快的。你可以声称没有“真正的”系统调用。

顺便说一句,您可以add对内核进行虚拟系统调用(例如,某些系统调用始终返回0或hello world系统调用,另请参阅this)并进行测量。

答案 3 :(得分:1)

Brendan Gregg撰写的this benchmark(链接自this blog post,该主题有趣阅读)close(999)(或其他一些未使用的fd)建议使用。