繁体   English   中英

最快的Linux系统调用

[英]Fastest Linux system call

在支持syscallsysret的x86-64 Intel系统上,香草内核上来自64位用户代码的“最快”系统调用是什么?

特别是,它必须是一个执行syscall / sysret用户<->内核转换1的系统调用,但执行的工作量最少。 它甚至不需要执行syscall本身:某种从不分派给内核侧特定调用的早期错误是可以的,只要它不会因此而走慢。

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


1特别是,这不包括看似是系统调用但在VDSO中实现的东西(例如clock_gettime )或在运行时缓存的东西(例如getpid )。

不存在的一个,因此快速返回-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:

使用无效的系统呼叫号码,以便调度代码只需返回
eax = -ENOSYS而不是完全分派给系统调用处理函数。

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


这个答案是针对sysenter而不是syscall 这个问题原本说sysenter / sysret (这真是奇怪,因为sysexit去与sysenter ,而sysret去与syscall )。 我基于sysenter在x86-64内核上针对32位进程进行了回答。

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


如果在64位代码中使用32位int 0x80 Linux ABI,会发生什么情况? Q&A概述了从compat模式到x86-64内核( entry_64_compat.S )的系统调用入口点的内核。 这个答案只是其中的相关部分。

该答案中的链接都指向Linux 4.12源,该源不包含Meltdown缓解页表操作,因此这将产生大量额外开销。

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

在将寄存器推struct pt_regs以在内核堆栈上构造struct pt_regs ,有一个TRACE_IRQS_OFF钩子(不知道有多少条指令),然后call do_fast_syscall_32用C编写的call do_fast_syscall_32 。(本机64位syscall调度直接从asm完成,但是32位兼容系统调用始终通过C)分派。

arch/x86/entry/common.c do_syscall_32_irqs_on非常轻巧:仅检查是否正在跟踪进程(我认为这是strace可以通过ptrace挂接系统调用的方式),然后

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

    syscall_return_slowpath(regs);
}

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

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

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

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


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

在Brendan Gregg的这个基准测试中 (建议关注此主题的博客文章链接),建议使用close(999) (或其他未使用的fd)。

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

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

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

我怀疑(没有进行基准测试) getpid(2)应该是一个非常快速的系统调用,因为它唯一需要做的就是从内核内存中获取一些数据。 对于AFAIK,这是一个真正的系统调用,没有使用VDSO技术。 而且,您可以使用syscall(2)来避免由libc缓存并强制执行真正的系统调用。

我保持我的立场(在对您的最初问题的评论中给出):没有实际动力,您的问题就没有任何具体意义。 然后我仍然认为syscall(2)执行getpid可以衡量进行系统调用的典型开销(我想您真的很在乎那个)。 实际上,几乎所有系统调用都在做诸如getpid (或getppid )这样的工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM