Maison  >  Article  >  Opération et maintenance  >  Explication détaillée des opérations sur les fichiers dans la programmation serveur

Explication détaillée des opérations sur les fichiers dans la programmation serveur

PHP中文网
PHP中文网original
2017-06-20 11:57:021435parcourir

Tout sous le système Linux est un fichier. Toutes les couches sous-jacentes sont protégées via le mécanisme du système de fichiers virtuel (VFS). Les utilisateurs peuvent utiliser différents pilotes via une interface unifiée. Le descripteur de fichier est appliqué à ce moment. Le descripteur de fichier est similaire au handle sous Windows. La plupart des opérations sur le fichier sont effectuées via ce descripteur, comme la lecture et l'écriture. Pour chaque descripteur de fichier, le noyau utilise trois structures de données pour le gérer.

(1) Chaque processus a une entrée d'enregistrement dans la table des processus, et chaque entrée d'enregistrement a une table de descripteurs de fichiers ouverte, qui peut être traitée comme un vecteur , chaque descripteur occupe une entrée. À chaque descripteur de fichier est associé :

 (a) Indicateur de descripteur de fichier. (Actuellement, un seul indicateur de descripteur de fichier FD_CLOEXEC est défini)

 (b) Pointeur vers une entrée de table de fichiers.

(2) Le noyau maintient une table de fichiers pour tous les fichiers ouverts. Chaque entrée de la table de fichiers contient :

  (a) Indicateur d'état du fichier (lecture, écriture, ajout-écriture, synchronisation, non bloquant, etc.).

 (b) Déplacement actuel du fichier. (C'est-à-dire la valeur opérée par la fonction lseek)

 (c) Pointeur vers l'entrée du nœud v du fichier.

(3) Chaque fichier ouvert (ou périphérique) a une structure de nœuds v. Le nœud v contient le type de fichier et les informations de pointeur des fonctions qui effectuent diverses opérations sur ce fichier. Pour la plupart des fichiers, le nœud v contient également le nœud i (nœud d'index) du fichier. Ces informations sont lues du disque vers la mémoire lorsque le fichier est ouvert, de sorte que toutes les informations sur le fichier sont rapidement disponibles. Par exemple, l'i-node contient le propriétaire du fichier, la longueur du fichier, le périphérique sur lequel réside le fichier, un pointeur vers les blocs de données réels utilisés par le fichier sur le disque, etc.

Après l'encapsulation à trois couches du système de fichiers ci-dessus, chaque couche est responsable de différentes responsabilités, de haut en bas en bas, la première couche est utilisée pour identifier les fichiers, la deuxième couche est utilisée pour gérer les données indépendantes du processus et la troisième couche gère les métadonnées du système de fichiers et est directement associée à un fichier. L’un des avantages de cette idée en couches est que la couche supérieure peut réutiliser la structure de la couche inférieure. Il peut y avoir plusieurs entrées de descripteur de fichier pointant vers la même entrée de table de fichiers, et plusieurs entrées de table de fichiers pointant vers le même nœud V.

Si deux processus indépendants ouvrent le même fichier, chaque processus qui ouvre le fichier obtiendra une entrée de table de fichiers, mais les pointeurs de nœud V des deux entrées de table de fichiers pointent vers le même V nœud, cette disposition permet à chaque processus d'avoir son propre déplacement actuel du fichier et prend en charge différentes méthodes d'ouverture (O_RDONLY, O_WRONLY, ORDWR).

Lorsqu'un processus crée un processus enfant via fork, les descripteurs de fichiers dans les processus parent et enfant partagent la même entrée de table de fichiers, c'est-à-dire les descripteurs de fichiers du parent et processus enfants pointant dans la même direction. Généralement, nous fermerons le fd dont nous n'avons pas besoin après fork. Par exemple, lorsque les processus parent et enfant communiquent via un tube ou une paire de sockets, ils fermeront souvent la fin qu'ils n'ont pas besoin de lire (ou d'écrire). Ce n'est que lorsqu'il n'y a pas de descripteur de fichier faisant référence à l'entrée de fichier actuelle que l'opération de fermeture détruit en fait la structure de données de l'entrée de fichier actuelle, ce qui est quelque peu similaire à l'idée du comptage de références. C'est également la différence entre les fonctions de fermeture et d'arrêt dans la programmation réseau. La première ne se déconnecte véritablement que lorsque le dernier processus utilisant le handle de socket est fermé, tandis que la seconde déconnecte directement un côté de la connexion sans aucune discussion. Cependant, dans un environnement multithread, puisque les threads parent et enfant partagent l'espace d'adressage, les descripteurs de fichiers sont détenus conjointement et il n'y a qu'une seule copie, vous ne pouvez donc pas fermer le fd dont vous n'avez pas besoin dans le thread, sinon il entraînera la fermeture d'autres fichiers nécessitant la fermeture du fd. Les threads seront également affectés. Étant donné que les descripteurs de fichiers ouverts dans les processus parent et enfant partagent la même entrée de table de fichiers, dans la programmation serveur de certains systèmes, si le modèle preforking est utilisé (le serveur pré-dérive plusieurs processus enfants, et chaque processus enfant écoute listeningfd pour accepter la connexion) Cela entraînera l'apparition du phénomène de troupeau tonitruant. Plusieurs sous-processus dérivés du serveur acceptent chaque appel et sont donc mis en veille. Lorsque la première connexion client arrive, même si un seul processus obtient la connexion, tous les processus. sont réveillés, ce qui entraîne une baisse des performances. Voir UNP P657.

En même temps, si exec est appelé après fork, tous les descripteurs de fichiers continueront à rester ouverts. Cela peut être utilisé pour transmettre certains descripteurs de fichiers au programme après l'exécution. En même temps, l'indicateur de descripteur de fichier FD_CLOEXEC est une option utilisée pour conserver les descripteurs de fichiers ouverts lors de la fermeture de exec.

Vous pouvez également copier explicitement un descripteur de fichier via dup ou fcntl, et ils pointent vers la même entrée de table de fichiers. Copiez le descripteur de fichier à la valeur spécifiée via dup2.

Chaque processus possède une table de descripteurs de fichiers, qui est indépendante entre les processus. Il n'y a pas de relation directe entre les descripteurs de fichiers entre les deux processus, la description du fichier peut donc être transmise directement dans le fichier. process., mais il perd son sens s'il est transmis à travers les processus. Unix peut transmettre des descripteurs de fichiers spéciaux via sendmsg/recvmsg (voir UNP Section 15.7). Les trois premiers descripteurs de fichiers de chaque processus correspondent à l'entrée standard, à la sortie standard et à l'erreur standard. Cependant, il existe une limite au nombre de descripteurs de fichiers pouvant être ouverts par un processus. S'il y a trop de descripteurs de fichiers ouverts, le problème « Trop de fichiers ouverts » se produira. Sur le serveur réseau, lorsque accept est appelé via Listenfd, une erreur EMFILE est générée principalement parce que le descripteur de fichier est une ressource importante du système. Le système limite la valeur par défaut du descripteur de fichier. un seul processus. Il s'agit de 1024 et peut être visualisé à l'aide de la commande ulimit -n. Bien sûr, vous pouvez également augmenter le nombre de descripteurs de fichiers de processus, mais il s'agit d'une solution temporaire plutôt que permanente, car lorsqu'il s'agit de services à haute concurrence, les ressources du serveur sont limitées et l'épuisement des ressources est inévitable.

Lorsqu'il est combiné avec la méthode de déclenchement horizontal d'epoll pour écouter les connexions lisenfd, un grand nombre de connexions socket inonderont la file d'attente des connexions TCP si elles ne sont pas traitées, et Listenfd générera toujours des événements lisibles. Pour mettre le serveur en attente, Chen Shuo, l'auteur de la bibliothèque réseau open source C++ muduo, utilise la méthode de préparation d'un descripteur de fichier inactif à l'avance. Lorsqu'une erreur EMFILE se produit, il ferme d'abord le fichier inactif et obtient un. quota de descripteur de fichier, puis l'accepte. Le descripteur de fichier d'une connexion socket est ensuite fermé immédiatement, déconnectant ainsi gracieusement la connexion du client, et enfin rouvrir le fichier inactif pour combler le "trou" au cas où cette situation se reproduirait.

 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 }

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn