Eine, die nicht existiert und daher schnell -ENOSYS zurückgibt.
Von 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:
Verwenden Sie eine ungültige Systemrufnummer, damit der Dispatching-Code einfach mit zurückkehrt
eax = -ENOSYS
anstatt überhaupt an eine Systemaufrufbearbeitungsfunktion zu senden.
Es sei denn, dies veranlasst den Kernel, den iret
zu verwenden langsamer Pfad statt sysret
/ sysexit
. Das könnte die Messungen erklären, die eine ungültige Zahl zeigen, die 17 Zyklen langsamer als syscall(SYS_getpid)
ist , da die glibc-Fehlerbehandlung (Einstellung errno
) erklärt es wahrscheinlich nicht. Aber aus meiner Lektüre der Kernel-Quellen sehe ich keinen Grund, warum es nicht immer noch sysret
verwenden würde während -ENOSYS
zurückgegeben wird .
Diese Antwort ist für sysenter
, nicht syscall
. Die Frage lautete ursprünglich sysenter
/ sysret
(was seltsam war, weil sysexit
passt zu sysenter
, während sysret
passt zu syscall
). Ich habe basierend auf sysenter
geantwortet für einen 32-Bit-Prozess auf einem x86-64-Kernel.
Natives 64-Bit syscall
innerhalb des Kernels effizienter gehandhabt wird. (Aktualisierung; mit Meltdown/Spectre-Minderungspatches wird es immer noch über C do_syscall_64
versendet in 4.16-rc2).
My Was passiert, wenn Sie die 32-Bit-Int 0x80-Linux-ABI in 64-Bit-Code verwenden? Q&A gibt einen Überblick über die Kernel-Seite der Einstiegspunkte für Systemaufrufe aus dem Kompatibilitätsmodus in einen x86-64-Kernel (entry_64_compat.S
). Diese Antwort übernimmt nur die relevanten Teile davon.
Die Links in dieser und dieser Antwort verweisen auf Linux 4.12-Quellen, die keine Seitentabellenmanipulation zur Meltdown-Mitigation enthalten, sodass dies signifikant ist zusätzlicher Overhead.
int 0x80
und sysenter
unterschiedliche Einstiegspunkte haben. Sie suchen nach entry_SYSENTER_compat
. AFAIK, sysenter
geht immer dorthin, auch wenn Sie es in einem 64-Bit-User-Space-Prozess ausführen. Der Einstiegspunkt von Linux gibt eine Konstante __USER32_CS
aus als gespeicherter CS-Wert, sodass er immer im 32-Bit-Modus in den Benutzerbereich zurückkehrt.
Nach dem Drücken von Registern zum Erstellen eines struct pt_regs
Auf dem Kernel-Stack gibt es einen TRACE_IRQS_OFF
Hook (keine Ahnung, wie viele Anweisungen das sind), dann call do_fast_syscall_32
die in C geschrieben ist. (Native 64-Bit syscall
die Zuteilung erfolgt direkt von asm, aber 32-Bit-kompatible Systemaufrufe werden immer über C) zugestellt.
do_syscall_32_irqs_on
in arch/x86/entry/common.c
ist ziemlich leicht:nur eine Überprüfung, ob der Prozess verfolgt wird (ich denke, so funktioniert strace
kann Systemaufrufe über ptrace
einhängen ), dann
...
if (likely(nr < IA32_NR_syscalls)) {
regs->ax = ia32_sys_call_table[nr]( ... arg );
}
syscall_return_slowpath(regs);
}
AFAIK, der Kernel kann sysexit
verwenden nachdem diese Funktion zurückkehrt.
Der Rückweg ist also derselbe, unabhängig davon, ob EAX eine gültige Systemaufrufnummer hatte oder nicht, und offensichtlich ist die Rückkehr ohne Versand der schnellste Weg durch diese Funktion, insbesondere in einem Kernel mit Spectre-Minderung, bei dem die indirekte Verzweigung auf der Tabelle der Funktionszeiger erfolgt würde durch eine Retpoline gehen und immer falsch vorhersagen.
Wenn Sie sysenter/sysexit ohne all diesen zusätzlichen Aufwand wirklich testen möchten, müssen Sie Linux so modifizieren, dass es einen viel einfacheren Einstiegspunkt setzt, ohne auf Tracing zu prüfen oder alle Register zu pushen / zu poppen.
Sie möchten wahrscheinlich auch die ABI ändern, um eine Absenderadresse in einem Register zu übergeben (wie syscall
von selbst macht) anstatt auf dem User-Space-Stack gespeichert zu werden, was Linuxs aktuelles sysenter
ist ABI tut; es muss get_user()
sein um den EIP-Wert zu lesen, zu dem es zurückkehren soll.
Wenn all dieser Overhead Teil dessen ist, was Sie messen möchten, sind Sie definitiv mit einem eax fertig, das Ihnen -ENOSYS
liefert; im schlimmsten Fall erhalten Sie einen zusätzlichen Verzweigungsfehler bei der Bereichsprüfung, wenn Verzweigungsvorhersagen für diese Verzweigung heiß sind, basierend auf normalen 32-Bit-Systemaufrufen.
In diesem Benchmark von Brendan Gregg (verlinkt von diesem Blogbeitrag, der eine interessante Lektüre zu diesem Thema ist) close(999)
(oder ein anderes nicht verwendetes fd) wird empfohlen.