Wie erstellt die Shell/Init die Standard-Streams?

Wie erstellt die Shell/Init die Standard-Streams?

Ich lese gerade die Quelle des MITxv6-Betriebssystem. Dieser Ausschnitt steht am Anfang von sh.c:

// Ensure that three file descriptors are open.
while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
}

Ich verstehe, dass hier geprüft wird, obmindestens3 Datei-Deskriptoren sind geöffnet (vermutlich für stdin, stdout und stderr), indem überprüft wird, ob der neu zugewiesene Datei-Deskriptor über 3 liegt (oder gleich 3 ist).

1) Wie ist es möglich, openvom selben Prozess aus mehrmals auf dasselbe Gerät zuzugreifen und unterschiedliche Dateideskriptoren zu erwarten?

2) Um dies zu verstehen, habe ich einen ähnlichen Codeausschnitt auf meinem Hostcomputer (x86_64 Linux 4.6.0.1) ausgeführt. Das Testprogramm hat wiederholt openeine Textdatei in einer Schleife bearbeitet, um zu sehen, ob wir einen anderen Dateideskriptor erwarten können, aber es hat immer denselben Dateideskriptor erzeugt. Daraus habe ich geschlossen, dass sich opendie Bearbeitung einer echten Datei und eines Geräts (wie /dev/console) irgendwie unterscheidet, da der Codeausschnitt von xv6 offensichtlich funktioniert (getestet in Qemu). Was genau ist der Unterschied?

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int cnt = 0;

    while ((fd = open("sample.txt", O_RDWR) > 0)) {
        if (cnt != 10) {
            cnt++;
            printf("File descriptor opened: %d\n", fd);
        } else {
            break;
        }
    }

    return 0;
}

Hier ist die Ausgabe beim Ausführen:

$ ./a.out
File descriptor opened: 1
File descriptor opened: 1
[snip]
File descriptor opened: 1
File descriptor opened: 1

BEARBEITENBasierend auf einer der Antworten habe ich stracedie ausführbare Datei ausgeführt und festgestellt, dassopen In der Tatgibt mehrere Dateideskriptoren zurück, die aber aus irgendeinem Grund nicht alle gedruckt werden. Warum ist das so?

3) Etwas unzusammenhängend, aber ist die Konvention, stdio-Streams in fds 0-2 zu verwenden, nicht genau das – eine Konvention? Wenn beispielsweise die Initialisierungssequenz die Eingabe-/Ausgabedateideskriptoren etwas anderem zuweist – würde dies irgendwie Auswirkungen darauf haben, wie die untergeordneten Elemente ihre E/A durchführen?

Antwort1

Das sind eigentlich 3 Fragen. Entsorgen Sie Nr. 2 sofort, da das Programm falsch ist:

    while ((fd = open("sample.txt", O_RDWR) > 0)) {

du meintest wahrscheinlich

    while ((fd = open("sample.txt", O_RDWR)) > 0) {

mit den falsch platzierten Klammern testen Sie nur, ob fdgrößer als Null ist (was wahrscheinlich eine gute Annahme ist, da die Datei-Deskriptoren 0, 1 und 2 offen sind).

Zu #1: dieopenDer Aufruf (bei Erfolg) ist so definiert, dass er eindeutige Dateideskriptoren zurückgibt. Wenn das Gerät nicht erneut geöffnet werden kann, openwird zurückgegeben -1.

Zu #3: Klar, das ist einKonvention, sondern auch in derPOSIXStandard. Andere Systeme haben andere Konventionen verwendet, darunter einen vierten offenen Stream für jedes Programm.

Weiterführende Literatur:Verwenden Ihrer Aegis-Umgebung (Juli 1988)
Siehe Seite 6-9, auf der steht, dass Apollo Domain/OS einen Fehler hatteEingangund **Ausgabe*

Antwort2

Nein, der CodeüberprüfenDeskriptoren, es öffnet sie tatsächlich. Es werden noch keine Deskriptoren ausgegeben. Bei jedem Öffnen wird ein neuer Dateideskriptor ausgegeben, z. B. 0,1,2,3. Der Code bricht ab, wenn fd 3 erreicht wird, und lässt 0 bis 2 offen.

Jeder Dateideskriptor ist einfach ein Zeiger auf eine Stelle in einer Datei. Daher ist es kein Problem, mehr als einen Deskriptor für dieselbe Datei zu haben.

Wenn Ihr Testprogramm bei verschiedenen offenen Aufrufen das gleiche FD ausgibt, liegt ein Fehler vor. Bitte zeigen Sie den Code.

Ja, es gibt eine strenge Konvention für fd 0 bis 2. Wenn ein Code auf stdout drucken möchte, wird er tatsächlich auf fd 1 gedruckt. Es gibt keine Möglichkeit, stdout auf etwas anderes „abzubilden“.

Antwort3

1) Wenn das System das gleichzeitige Öffnen derselben Datei von mehreren Prozessen aus unterstützt, warum sollte man dann nicht auch einem Prozess das mehrmalige Öffnen erlauben? Da Dateideskriptoren vererbt werden, könnte es ohnehin passieren, dass dieselbe Datei zweimal im selben Prozess vorhanden ist, d. h. wenn sie einmal vererbt und einmal vom Prozess selbst geöffnet wird. Zu prüfen, welche Dateien der Prozess geöffnet hat, und einen Verweis auf die frühere Datei zurückzugeben, wäre zusätzliche Arbeit.

Außerdem stellt sich die Frage, ob verschiedene Teile des Prozesses gleichzeitig dieselbe Datei verwenden. Nehmen wir an, eine Bibliothek verwendet eine Konfigurationsdatei zur gleichen Zeit, in der das Hauptprogramm sie verwendet. Der Dateideskriptor ist an den Zugriffsmodus und die Position des Dateizeigers gebunden. Wenn es nur eine einzige Kopie davon gäbe, würden seltsame Dinge passieren. Und wenn die Datei zweimal geöffnet wurde (mit demselben Dateideskriptor), was sollte passieren, wenn sie geschlossen wird? Es könnte eine weitere Ebene der Referenzzählung geben, um zu entscheiden, wannWirklichSchließen Sie die Datei, aber das würde bei den anderen Problemen nicht helfen.


2) Hängt von Ihrem Code ab. Eine intelligente Sprache (also nicht C) könnte die Variable, die die geöffnete Datei enthält, neu zählen und sie schließen, kurz bevor Sie die Datei erneut öffnen. Schwer zu sagen, ohne den Code zu sehen.

Aber ein kleiner Test: Das zweimalige Öffnen derselben Datei mit derselben Variable in Perl führt zur gleichen FD-Nummer:

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open F, "test.txt"; printf "%d\n", fileno(F)'
3 3

Beim Ausführen stracewird angezeigt, dass die Datei unmittelbar vor dem erneuten Öffnen geschlossen wird.

Mit zwei verschiedenen Variablen erhalten wir zwei FD-Zahlen:

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open G, "test.txt"; printf "%d\n", fileno(G)'
3 4

3) Technisch gesehen könnte man sagen, dass die Standarddateinummern eine Konvention sind. Eine Konvention, die kodifiziert ist inPOSIX und der ISO-C-Standard:

Beim Programmstart sind drei Streams vordefiniert und müssen nicht explizit geöffnet werden: Standardeingabe (zum Lesen konventioneller Eingaben), Standardausgabe (zum Schreiben konventioneller Ausgaben) und Standardfehler (zum Schreiben diagnostischer Ausgaben).

Aber es ist trotzdem eine Konvention, da es möglich sein kann, ein Programm ohne sie auszuführen, ohne dass der Kernel etwas dagegen hat. Oder auch nicht: Wenn man die Spezifikation für die execAufrufe liest, scheint esermöglicht eine Implementierung, etwas für Sie zu öffnen:

Wenn die Dateideskriptoren 0, 1 oder 2 nach einem erfolgreichen Aufruf einer Funktion der Exec-Familie andernfalls geschlossen würden, können Implementierungen eine nicht angegebene Datei für den Dateideskriptor im neuen Prozessabbild öffnen.

(Sie können sie natürlich im Programm selbst schließen.)

Wenn Sie es so einrichten, dass sie beim Start nicht angezeigt werden, ist die Kompatibilität dahin:

Wenn ein Standarddienstprogramm oder eine konforme Anwendung ausgeführt wird und der Dateideskriptor 0 nicht zum Lesen geöffnet ist oder die Dateideskriptoren 1 oder 2 nicht zum Schreiben geöffnet sind, gilt die Umgebung, in der das Dienstprogramm oder die Anwendung ausgeführt wird, als nicht konform und das Dienstprogramm oder die Anwendung verhält sich folglich möglicherweise nicht wie in diesem Standard beschrieben.

DerLinux-Manpages drücken es etwas praktischer aus:

Als allgemeines Prinzip kann kein portables Programm, ob mit oder ohne Privilegien, davon ausgehen, dass diese drei Datei-Deskriptoren während eines execve() geschlossen bleiben.

verwandte Informationen