最快的Linux系统调用

Bee*_*ope 8 linux performance x86-64 microbenchmark

在支持的英特尔x86-64的系统syscallsysret什么是从64位用户代码"最快"的系统调用在香草内核?

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

这样的调用可用于估计原始syscallsysret开销,而与呼叫完成的任何工作无关.


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

Tim*_*win 9

一个不存在的,因此快速返回-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:
Run Code Online (Sandbox Code Playgroud)


Pet*_*des 5

使用无效的系统调用号码,以便调度代码仅返回
eax = -ENOSYS而不是根本不调度至系统调用处理函数。

除非这导致内核使用iret慢速路径代替sysret/ sysexit。这可能可以解释测量结果,显示无效数字比慢17个周期syscall(SYS_getpid),因为glibc错误处理(设置errno)可能没有解释它。但是从我阅读内核源代码的过程中,我看不出sysret在返回时仍不使用它的任何原因-ENOSYS


这个答案是为了sysenter,不是syscall。问题最初是sysenter/的sysret(这很奇怪,因为sysexit与一起使用sysenter,而sysret与一起使用syscall)。我回答基于sysenterx86-64内核上的32位进程。

syscall在内核内部更有效地处理本机64位。(更新;使用Meltdown / Spectre缓解补丁,它仍通过do_syscall_64 4.16-rc2中的C调度)。


如果使用32位int 0x80的Linux的ABI的64位代码,会发生什么?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位兼容系统呼叫始终通过C)分派。

do_syscall_32_irqs_oninarch/x86/entry/common.c非常轻巧:只需检查是否正在跟踪进程(我认为这是如何strace通过钩住系统调用ptrace),然后

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

    syscall_return_slowpath(regs);
}
Run Code Online (Sandbox Code Playgroud)

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

因此,无论EAX是否具有有效的系统调用号,返回路径都是相同的,并且显然返回而不进行分派是通过该函数的最快路径,尤其是在具有Spectre缓解功能的内核中,函数指针表上的间接分支会经历一次反覆陈述,并且总是会错误地预测。

如果您想真正测试sysenter / sysexit而没有所有额外的开销,则需要修改Linux以放置一个更简单的入口点,而无需检查跟踪或压入/弹出所有寄存器。

您可能还希望修改ABI以在寄存器中传递一个返回地址(就像syscall它自己一样),而不是像Linux当前的sysenterABI那样保存在用户空间堆栈中。它必须get_user()读取应该返回的EIP值。


如果所有这些开销都属于您要衡量的一部分,那么肯定已经设置了可以给您带来收益的窍门-ENOSYS;在最坏的情况下,如果正常的32位系统调用基于该分支的分支预测变量很热,那么从范围检查中您将得到一个额外的分支未命中。

  • 您应该忽略上面的数字,我终于意识到我只是在计时用户模式周期。实际时间约为1800个周期,因此我需要仔细检查我的工作,因为这似乎太慢了。大部分成本是一个`wrmsr`来注册0x48,我认为这是一个Spectre缓解措施。我不知道如何关闭它。 (2认同)
  • 并非所有缓解措施都具有关闭开关,就像KPTI一样,您可以在启动时通过`noibrs`和`noibpb`将其关闭。实际上,只要发行版中包含发行版,您就可以在启动后在/ sys / kernel文件系统中动态地执行该操作。禁用这些功能后,系统调用成本将降低到至少约160个周期。 (2认同)
  • 请注意,[/ sys / kernel]开关(在此处[https://access.redhat.com/articles/3311301]中描述)显然仅在RHEL派生内核上可用,至少目前是这样。但是,您仍然可以使用boot参数在几乎所有主线派生的内核上禁用它们(但是,使用和不使用选项进行背对背测试都比较烦人)。 (2认同)