Usermode Switch zu Kernelfunktionsaufruf beim Lesen einer Datei - Wieviele Bytes werden übertragen?



  • Wie läuft das eigentlich ab, wenn ein Programm im Userspace auf eine Datei zum Lesen zugreifen möchte und dann deswegen eine Kernelfunktion zum Lesen der Datei aufruft.

    Wieviele Bytes werden dann tatsächlich eingelesen und dann an das Userspaceprogramm zurückgeschickt?

    Oder ist das so, dass jeder Schritt ein Byte weiter in der Datei einen Kernelfunktions mit komplettem Prozessswitch auslöst?

    Oder delegiert der Kernel den Zugriff auf die Datei an die Anwendung nach Überprüfung der Rechte, so dass die Anwendung direkt auf dem Datenträger im erlaubten Bereich herumfuchteln darf?

    Wie ist das genau gelöst?



  • Kernelfunktionalität wird nicht durch einen normalen Funktionsaufruf in Anspruch genommen. Der Vorgang nennt sich Systemcall und läuft ein wenig anders ab. Wie genau ist zwar architekturspezifisch, das Grundprinzip ist aber meist ähnlich.

    Das Userspace-Programm führt eine bestimmte CPU-Instruktion (z.B. swi bei ARM, syscall bei x86-64) aus, die zum Auslösen eines Software-Interrupts führt. In der Regel hat das zur Folge, das die CPU in einen privilegierten Modus wechselt und an eine bestimmte Stelle im Speicher springt. An dieser Stelle hat der Startcode des Kernels zuvor einen Verweis auf ein Stück Code hinterlegt, das jetzt zur Ausführung kommt: der Syscall-Handler.

    Dieser muss jetzt irgendwie herausfinden, welchen Systemcall dein Programm überhaupt ausführen wollte. Hierzu hat dein Programm zuvor eine bestimmte Zahl (Syscall-Nr.) an einen vereinbarten Ort geschrieben (meist ein bestimmtes Register). Dieses benutzt der allgemeine Syscall-Handler um die Implementierung des entsprechenden Syscalls aufzurufen.

    Die Systemcall-Implementierung weiß welche Argumente sie benötigt und wo diese zu finden sind (meist ebenfalls wieder festgelegte Register). Dein Programm muss die Argumente vorher dort hinkopiert haben.

    Der Kernel überprüft jetzt ob die Argumente korrekt sind und du für die entsprechende Operation berechtigt bist. Wenn ja, wird sie durchgeführt und die CPU wieder in den unprivilegierten Modus geschaltet. Der Kontrollfluss landet letztendlich wieder bei deinem Programm.

    Jetzt wunderst du dich vielleicht, dass du von diesem ganzen Theater gar nichts mitkriegst, wenn du ein Programm schreibst. Das liegt daran, dass die C-Library deines Systems Wrapper für die Systemcalls des Kernels mitbringt. Dies sind kleine Funktionen (oft ganz oder zumindest teilweise in Assembler geschrieben), die du wie ganz normale C-Funktionen aufrufen kannst. Diese kopieren die übergebenen Argumente an die richtige Stelle und lösen dann den oben beschriebenen Vorgang aus. Das erspart es dir, den lästigen architekturspezfischen Syscall-Code selbst zu implementieren.

    Zu deiner spezifischen Frage zu Dateien: wie sieht denn so ein Systemcall zum Lesen von Dateien aus (als C-Deklaration)?

    ssize_t read(int fd, void *buf, size_t n);
    

    Du übergibst hier also einen Zeiger auf einen Speicherbereich (buf) und die entsprechende Anzahl Bytes, die gelesen werden sollen (n). Der Kernel liest nun maximal n Bytes vom Datenträger und kopiert sie dir in deinen Puffer. Sofern du für n nicht 1 angibst, muss also nicht für jedes einzelne Byte ein Wechsel in den Kernelmode erfolgen. In der Tat wäre es sehr ineffizient dies so zu tun.



  • Achso, übrigens: C-Library-IO-Funktionen wie getchar(3), fgets(3), usw. sind in der Regel so implementiert, dass sie nicht für jeden Aufruf einen extra Systemaufruf machen. Stattdessen lesen sie beim ersten Aufruf gleich einen viel größeren Bereich in einen Puffer (z.B. 4 KiB) und liefern dir erstmal nur das erste Byte davon zurück. Bei zukünftigen Aufrufen erhältst du dann den Rest, solange bis der Puffer aufgebraucht ist und wieder ein read-Systemcall für bspw. 4 KiB durchgeführt wird. Dieses Vorgehen nennt man Buffered IO. Der zugehörige Puffer kann z.B. Bestandteil der FILE-Struktur sein und wird beim Öffnen der Datei angelegt.


Anmelden zum Antworten