Bee*_*ope 8 linux performance x86-64 microbenchmark
在支持的英特尔x86-64的系统syscall
和sysret
什么是从64位用户代码"最快"的系统调用在香草内核?
特别是,它必须是一个系统调用来执行syscall
/ sysret
user < - >内核转换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
)。我回答基于sysenter
x86-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_on
inarch/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当前的sysenter
ABI那样保存在用户空间堆栈中。它必须get_user()
读取应该返回的EIP值。
如果所有这些开销都属于您要衡量的一部分,那么肯定已经设置了可以给您带来收益的窍门-ENOSYS
;在最坏的情况下,如果正常的32位系统调用基于该分支的分支预测变量很热,那么从范围检查中您将得到一个额外的分支未命中。
归档时间: |
|
查看次数: |
1464 次 |
最近记录: |