Alles unter dem Linux-System ist eine Datei, die durch den virtuellen Dateisystemmechanismus (VFS) geschützt ist. Benutzer können verschiedene Treiber über eine einheitliche Schnittstelle bedienen Zu diesem Zeitpunkt wird der Dateideskriptor angewendet. Der Dateideskriptor ähnelt dem Handle unter Windows. Die meisten Vorgänge an der Datei werden über diesen Deskriptor ausgeführt. Für jeden Dateideskriptor verwendet der Kernel drei Datenstrukturen, um ihn zu verwalten.
(1) Jeder Prozess verfügt über einen Datensatzeintrag in der Prozesstabelle, und jeder Datensatzeintrag verfügt über eine offene Dateideskriptortabelle, die Als Vektor behandelt werden kann , jeder Deskriptor belegt einen Eintrag. Jedem Dateideskriptor ist Folgendes zugeordnet:
(a) Dateideskriptor-Flag. (Derzeit ist nur ein Dateideskriptor-Flag FD_CLOEXEC definiert)
(b) Zeiger auf einen Dateitabelleneintrag.
(2) Der Kernel verwaltet eine Dateitabelle für alle geöffneten Dateien. Jeder Dateitabelleneintrag enthält:
(a) Dateistatus-Flag (Lesen, Schreiben, Add-Write, Synchronisierung, Nichtblockierung usw.).
(b) Aktuelle Dateiverschiebung. (Das heißt, der von der lseek-Funktion verwaltete Wert)
(c) Zeiger auf den V-Node-Eintrag der Datei.
(3) Jede geöffnete Datei (oder jedes geöffnete Gerät) hat eine V-Knotenstruktur. Der v-Knoten enthält den Dateityp und Zeigerinformationen von Funktionen, die verschiedene Vorgänge an dieser Datei ausführen. Bei den meisten Dateien enthält der v-Knoten auch den i-Knoten (Indexknoten) der Datei. Diese Informationen werden beim Öffnen der Datei von der Festplatte in den Speicher gelesen, sodass alle Informationen zur Datei schnell verfügbar sind. Der I-Node enthält beispielsweise den Eigentümer der Datei, die Länge der Datei, das Gerät, auf dem sich die Datei befindet, einen Zeiger auf die tatsächlichen Datenblöcke, die von der Datei auf der Festplatte verwendet werden, und so weiter.
Nach der dreischichtigen Kapselung des oben genannten Dateisystems ist jede Schicht von oben nach unten für unterschiedliche Verantwortlichkeiten verantwortlich Unten, die erste Schicht wird zur Identifizierung von Dateien verwendet, die zweite Schicht dient zur Verwaltung prozessunabhängiger Daten und die dritte Schicht verwaltet Dateisystem-Metadaten und ist direkt mit einer Datei verknüpft. Ein Vorteil dieser Schichtidee besteht darin, dass die obere Schicht die Struktur der unteren Schicht wiederverwenden kann. Es können mehrere Dateideskriptoreinträge vorhanden sein, die auf denselben Dateitabelleneintrag verweisen, und es können mehrere Dateitabelleneinträge vorhanden sein, die auf denselben V-Knoten verweisen.
Wenn zwei unabhängige Prozesse dieselbe Datei öffnen, erhält jeder Prozess, der die Datei öffnet, einen Dateitabelleneintrag, aber die V-Knotenzeiger der beiden Dateitabelleneinträge zeigen auf dasselbe V Knoten ermöglicht diese Anordnung jedem Prozess eine eigene aktuelle Verschiebung der Datei und unterstützt verschiedene Öffnungsmethoden (O_RDONLY, O_WRONLY, ORDWR).
Wenn ein Prozess über Fork einen untergeordneten Prozess erstellt, teilen sich die Dateideskriptoren im übergeordneten und untergeordneten Prozess denselben Dateitabelleneintrag, dh die Dateideskriptoren der übergeordneten und untergeordneten Prozesse Untergeordnete Prozesse zeigen in die gleiche Richtung. Im Allgemeinen schließen wir den fd, den wir nach der Verzweigung nicht benötigen. Wenn beispielsweise der übergeordnete und der untergeordnete Prozess über Pipe oder Socketpair kommunizieren, schließen sie häufig das Ende, das sie nicht lesen (oder schreiben) müssen. Nur wenn kein Dateideskriptor vorhanden ist, der auf den aktuellen Dateieintrag verweist, zerstört der Schließvorgang tatsächlich die Datenstruktur des aktuellen Dateieintrags, was der Idee der Referenzzählung etwas ähnelt. Dies ist auch der Unterschied zwischen den Close- und Shutdown-Funktionen in der Netzwerkprogrammierung. Erstere trennt die Verbindung nur dann wirklich, wenn der letzte Prozess, der das Socket-Handle verwendet, geschlossen wird, während letztere eine Seite der Verbindung ohne Diskussion direkt trennt. Da jedoch in einer Multithread-Umgebung der übergeordnete und der untergeordnete Thread den Adressraum gemeinsam nutzen, sind die Dateideskriptoren im gemeinsamen Besitz und es gibt nur eine Kopie, sodass Sie nicht benötigte FDs im Thread nicht schließen können, da dies sonst der Fall ist Dies führt dazu, dass auch andere Dateien, die das Schließen von fd benötigen, betroffen sind. Da die in den übergeordneten und untergeordneten Prozessen geöffneten Dateideskriptoren denselben Dateitabelleneintrag gemeinsam nutzen, kann bei der Serverprogrammierung einiger Systeme das Preforking-Modell verwendet werden (der Server leitet mehrere untergeordnete Prozesse vorab ab, und jeder untergeordnete Prozess hört auf listenfd, um zu akzeptieren). Dies führt zum Auftreten des Thundering-Herd-Phänomens. Mehrere vom Server abgeleitete Unterprozesse nehmen jeden Anruf an und werden daher in den Ruhezustand versetzt, obwohl nur ein Prozess die Verbindung erhält werden geweckt, wodurch die Leistung leidet. Siehe UNP P657.
Gleichzeitig bleiben alle Dateideskriptoren weiterhin geöffnet, wenn exec nach dem Fork aufgerufen wird. Dies kann verwendet werden, um bestimmte Dateideskriptoren nach exec an das Programm zu übergeben. Gleichzeitig ist das Dateideskriptor-Flag FD_CLOEXEC eine Option, die verwendet wird, um den Dateideskriptor beim Schließen von exec offen zu halten.
Sie können einen Dateideskriptor auch explizit über dup oder fcntl kopieren und sie verweisen auf denselben Dateitabelleneintrag. Kopieren Sie den Dateideskriptor über dup2 auf den angegebenen Wert.
Jeder Prozess verfügt über eine Dateideskriptortabelle, die zwischen den Prozessen unabhängig ist. Es besteht keine direkte Beziehung zwischen den Dateideskriptoren zwischen den beiden Prozessen, sodass die Dateibeschreibung direkt innerhalb des Prozesses übergeben werden kann Prozessdeskriptor, aber er verliert an Bedeutung, wenn er über Prozesse weitergegeben wird. Unix kann spezielle Dateideskriptoren über sendmsg/recvmsg übergeben (siehe UNP-Abschnitt 15.7). Die ersten drei Dateideskriptoren jedes Prozesses entsprechen der Standardeingabe, der Standardausgabe und dem Standardfehler. Es gibt jedoch eine Grenze für die Anzahl der Dateideskriptoren, die von einem Prozess geöffnet werden können. Wenn zu viele offene Dateideskriptoren vorhanden sind, tritt das Problem „Zu viele offene Dateien“ auf. Wenn auf dem Netzwerkserver „accept“ über listenfd aufgerufen wird, tritt ein EMFILE-Fehler auf. Dies liegt hauptsächlich daran, dass der Dateideskriptor eine wichtige Ressource des Systems ist Der einzelne Prozess ist 1024 und kann mit dem Befehl ulimit -n angezeigt werden. Natürlich können Sie auch die Anzahl der Prozessdateideskriptoren erhöhen, dies ist jedoch eher eine vorübergehende als eine dauerhafte Lösung, da bei der Arbeit mit Diensten mit hoher Parallelität die Serverressourcen begrenzt sind und eine Ressourcenerschöpfung unvermeidlich ist.
In Kombination mit der horizontalen Triggermethode von epoll zum Abhören von lisenfd-Verbindungen überschwemmt eine große Anzahl von Socket-Verbindungen die TCP-Verbindungswarteschlange, wenn sie nicht verarbeitet werden, und listenfd generiert immer lesbare Ereignisse Um den Server in einen Auslastungsmodus zu versetzen, verwendet Chen Shuo, der Autor der C++-Open-Source-Netzwerkbibliothek, die Methode, im Voraus einen Leerlaufdateideskriptor vorzubereiten. Wenn ein EMFILE-Fehler auftritt, schließt er zunächst die Leerlaufdatei und erhält eine Der Dateideskriptor einer Socket-Verbindung wird dann sofort geschlossen, wodurch die Verbindung zum Client ordnungsgemäß getrennt wird und schließlich die inaktive Datei erneut geöffnet wird, um die „Lücke“ zu füllen, falls diese Situation erneut auftritt.
1 //在程序开头先”占用”一个文件描述符 2 3 int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); 4 ………… 5 6 //然后当出现EMFILE错误的时候处理这个错误 7 8 peerlen = sizeof(peeraddr); 9 connfd = accept4(listenfd, (struct sockaddr*)&peeraddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);10 11 if (connfd == -1)12 {13 if (errno == EMFILE)14 {15 close(idlefd);16 idlefd = accept(listenfd, NULL, NULL);17 close(idlefd);18 idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);19 continue;20 }21 else22 ERR_EXIT("accept4");23 }
Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung von Dateioperationen in der Serverprogrammierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!