[英]Fastest Linux system call
在支持syscall
和sysret
的x86-64 Intel系统上,香草内核上来自64位用户代码的“最快”系统调用是什么?
特别是,它必须是一个执行syscall
/ sysret
用户<->内核转换1的系统调用,但执行的工作量最少。 它甚至不需要执行syscall本身:某种从不分派给内核侧特定调用的早期错误是可以的,只要它不会因此而走慢。
这样的调用可用于估计原始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:
使用无效的系统呼叫号码,以便调度代码只需返回
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 0x80
和sysenter
具有不同的入口点。 您正在寻找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位系统调用,该分支的分支预测变量是否很热,那么从范围检查中您将获得一个额外的分支未命中。
某些系统调用甚至不会经过任何用户->内核转换,请阅读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.