GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Schnellster Linux-Systemaufruf

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.


Linux
  1. Umgang mit einer Linux-Kernel-Panik

  2. Linux – Aufrufmethoden für Systemaufrufe im neuen Kernel?

  3. Linux-Systemaufruftabelle oder Cheatsheet für Assembly

  4. x86_64 Assembly Linux-Systemaufruf-Verwirrung

  5. Rufen Sie eine Userspace-Funktion innerhalb eines Linux-Kernel-Moduls auf

Kali Linux-Systemanforderungen

Befehl zum Herunterfahren von Linux

Dmesg-Befehl unter Linux

Sysctl-Befehl unter Linux

Ist Linux ein Betriebssystem oder ein Kernel?

Linux-Kernel vs. Mac-Kernel