Bee*_*ope 8 linux performance x86-64 microbenchmark
在支持的英特尔x86-64的系统syscall和sysret什么是从64位用户代码"最快"的系统调用在香草内核?
特别是,它必须是一个系统调用来执行syscall/ sysretuser < - >内核转换1,但除此之外的工作量最少.它甚至不需要进行系统调用本身:某种类型的早期错误从未调度到内核端的特定调用,这是好的,只要它不会因为这样而走慢路径.
这样的调用可用于估计原始syscall和sysret开销,而与呼叫完成的任何工作无关.
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:
Run Code Online (Sandbox Code Playgroud)
使用无效的系统调用号码,以便调度代码仅返回
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 0x80并sysenter具有不同的切入点。您正在寻找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位系统调用基于该分支的分支预测变量很热,那么从范围检查中您将得到一个额外的分支未命中。