在x86-64英特尔系统上支持syscall
和sysret
"最快"在vanilla内核上使用64位用户代码进行系统调用?
特别是,它必须是系统调用,才能运用syscall
/ sysret
用户< - >内核转换 1 ,但除此之外的工作量最少。它甚至不需要进行系统调用本身:某种类型的早期错误,它永远不会调度到内核端的特定调用,这是好的,只要它不会因为这样而走慢路径。
此调用可用于估算原始syscall
和sysret
开销,与呼叫完成的任何工作无关。
1 特别是,这排除了似乎是系统调用但在VDSO中实现的内容(例如,clock_gettime
)或由运行时缓存(例如{{1 }})。
答案 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
(这很奇怪,因为sysexit
与sysenter
一致,而sysret
与syscall
一致。我基于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 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位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)建议使用。