Hintergrund:
Der Systemaufruf-Overhead ist viel größer als der Funktionsaufruf-Overhead (Schätzungen reichen von 20-100x), was hauptsächlich auf die Kontextumschaltung vom Benutzerbereich zum Kernelbereich und zurück zurückzuführen ist. Inline-Funktionen werden häufig verwendet, um Funktionsaufruf-Overhead zu sparen, und Funktionsaufrufe sind viel billiger als Syscalls. Es liegt auf der Hand, dass Entwickler einen Teil des Systemaufruf-Overheads vermeiden möchten, indem sie so viel Kernel-Betrieb wie möglich in einem Systemaufruf erledigen.
Problem:
Dies hat eine Menge von (? Überflüssig) Systemaufrufen wie geschaffen sendmmsg () , recvmmsg () sowie den chdir, offen, lseek und / oder Symlink Kombinationen wie: openat
, mkdirat
, mknodat
, fchownat
, futimesat
, newfstatat
, unlinkat
, fchdir
, ftruncate
, fchmod
, renameat
, linkat
, symlinkat
, readlinkat
, fchmodat
, faccessat
, lsetxattr
, fsetxattr
, execveat
, lgetxattr
, llistxattr
, lremovexattr
, fremovexattr
, flistxattr
, fgetxattr
, pread
, pwrite
etc ...
Nun hat Linux hinzugefügt, copy_file_range()
das offenbar Lese- und Schreibsyscalls kombiniert. Es ist nur eine Frage der Zeit, bis fcopy_file_range (), lcopy_file_range (), copy_file_rangeat (), fcopy_file_rangeat () und lcopy_file_rangeat () angezeigt werden Mehr. OK, Linus und die verschiedenen BSD-Entwickler ließen es nicht so weit kommen, aber mein Punkt ist, dass, wenn es einen Batch-Systemaufruf gäbe, all diese (die meisten?) Im Benutzerraum implementiert werden könnten und die Kernel-Komplexität reduziert werden könnte, ohne viel hinzuzufügen wenn irgendein Aufwand auf der libc-Seite.
Es wurden viele komplexe Lösungen vorgeschlagen, die einen speziellen Syscall-Thread für nicht blockierende Syscalls zur Stapelverarbeitung von Syscalls enthalten. Diese Methoden erhöhen jedoch die Komplexität sowohl des Kernels als auch des Benutzerbereichs auf die gleiche Weise wie libxcb im Vergleich zu libX11 (die asynchronen Aufrufe erfordern viel mehr Setup).
Lösung?:
Ein generisches Batching-System. Dies würde die größten Kosten (mehrere Moduswechsel) verringern, ohne die Komplexität, die mit einem spezialisierten Kernel-Thread verbunden ist (obwohl diese Funktionalität später hinzugefügt werden könnte).
Grundsätzlich gibt es im socketcall () syscall bereits eine gute Basis für einen Prototyp. Erweitern Sie es einfach von einem Array von Argumenten zu einem Array von Rückgaben, einem Zeiger auf ein Array von Argumenten (einschließlich der Syscall-Nummer), der Anzahl der Syscalls und einem Flags-Argument ... so etwas wie:
batch(void *returns, void *args, long ncalls, long flags);
Ein Hauptunterschied wäre, dass die Argumente der Einfachheit halber wahrscheinlich alle Zeiger sein müssten, damit die Ergebnisse früherer Systemaufrufe von nachfolgenden Systemaufrufen verwendet werden können (zum Beispiel der Dateideskriptor von open()
zur Verwendung in read()
/ write()
).
Einige mögliche Vorteile:
- weniger User Space -> Kernel Space -> User Space Switching
- mögliche Compiler-Schalter -fcombine-syscalls, um zu versuchen, automatisch zu stapeln
- optionales Flag für asynchronen Betrieb (fd zurückgeben, um sofort zu sehen)
- Fähigkeit zur Implementierung zukünftiger kombinierter Syscall-Funktionen im Userspace
Frage:
Ist es möglich, ein Batching-System zu implementieren?
- Vermisse ich einige offensichtliche Fallstricke?
- Überschätze ich die Vorteile?
Lohnt es sich für mich, ein Batching-System zu implementieren (ich arbeite nicht bei Intel, Google oder Redhat)?
- Ich habe meinen eigenen Kernel schon mal gepatcht, fürchte mich aber vor dem Umgang mit dem LKML.
- Die Geschichte hat gezeigt, dass selbst wenn etwas für "normale" Benutzer (Nicht-Firmen-Endbenutzer ohne Git-Schreibzugriff) von großem Nutzen ist, es möglicherweise nie im Upstream akzeptiert wird (unionfs, aufs, cryptodev, tuxonice, etc ...)
Verweise:
quelle
batch
Syscalls inbatch
Syscalls verschachteln, können Sie einen beliebig tiefen Aufrufbaum beliebiger Syscalls erstellen. Grundsätzlich können Sie Ihre gesamte Anwendung in einem einzigen Systemaufruf zusammenfassen.Antworten:
Ich habe es auf x86_64 versucht
Patch gegen 94836ecf1e7378b64d37624fbb81fe48fbd4c772: (auch hier https://github.com/pskocik/linux/tree/supersyscall )
Und es scheint zu funktionieren - ich kann mit nur einem Systemaufruf Hallo an fd 1 und Welt an fd 2 schreiben:
Grundsätzlich benutze ich:
als universeller syscall-prototyp, wie es auf x86_64 zu funktionieren scheint, ist mein "super" -syscall also:
Es gibt die Anzahl der versuchten Syscalls zurück (
==Nargs
wenn dasSUPERSYSCALL__continue_on_failure
Flag übergeben wird, andernfalls>0 && <=Nargs
) und Fehler beim Kopieren zwischen Kernel-Space und User-Space werden durch Segfaults anstelle der üblichen angezeigt-EFAULT
.Was ich nicht weiß, ist, wie sich dies auf andere Architekturen übertragen lässt, aber es wäre sicher schön, so etwas im Kernel zu haben.
Wenn dies für alle Bögen möglich wäre, stelle ich mir einen Userspace-Wrapper vor, der über einige Gewerkschaften und Makros Typensicherheit bietet (er könnte ein Gewerkschaftsmitglied basierend auf dem Syscall-Namen auswählen und alle Gewerkschaften würden dann in die 6 Longs konvertiert oder was auch immer das Äquivalent der Architektur de Jour der 6 langen sein würde).
quelle
open
inwrite
und tun könnenclose
. Das würde die Komplexität aufgrund von get / put_user etwas erhöhen, aber wahrscheinlich lohnt es sich. In Bezug auf die Portabilität von IIRC können einige Architekturen die Syscall-Register für die Args 5 und 6 überlasten, wenn ein Syscall mit 5 oder 6 Argumenten gestapelt wird. Das Hinzufügen von 2 zusätzlichen Args für die zukünftige Verwendung würde dies beheben und könnte in Zukunft für asynchrone Aufrufparameter verwendet werden, wenn ein SUPERSYSCALL__async Flag ist gesetztZwei Hauptprobleme, die einem sofort in den Sinn kommen, sind:
Fehlerbehandlung: Jeder einzelne Systemaufruf kann mit einem Fehler enden, der von Ihrem User-Space-Code überprüft und behandelt werden muss. Ein Batching-Aufruf müsste daher ohnehin nach jedem einzelnen Aufruf User-Space-Code ausführen, sodass die Vorteile des Batching von Kernel-Space-Aufrufen zunichte gemacht würden. Außerdem müsste die API sehr komplex sein (wenn überhaupt möglich zu entwerfen). Wie würden Sie beispielsweise die Logik ausdrücken, wenn der dritte Aufruf fehlschlägt, etwas tun und den vierten Aufruf überspringen, aber mit dem fünften fortfahren?
Viele "kombinierte" Aufrufe, die tatsächlich implementiert werden, bieten zusätzliche Vorteile, abgesehen davon, dass sie nicht zwischen Benutzer- und Kernel-Space wechseln müssen. Beispielsweise vermeiden sie häufig, Speicher zu kopieren und Puffer insgesamt zu verwenden (z. B. Daten direkt von einer Stelle im Seitenpuffer zu einer anderen zu übertragen, anstatt sie durch einen Zwischenpuffer zu kopieren). Dies ist natürlich nur für bestimmte Kombinationen von Aufrufen (z. B. Lesen-Schreiben) sinnvoll, nicht für beliebige Kombinationen von Gruppenaufrufen.
quelle