Le tampon sera-t-il automatiquement vidé sur le disque à la fin d'un processus?

21

Lorsque je redirige la sortie d'une commande vers un fichier (par exemple, echo Hello > file) ce fichier sera-t-il garanti d'avoir de telles données juste après la sortie de la commande? Ou existe-t-il encore une très petite fenêtre entre les sorties de commande et les données écrites dans le fichier? Je voudrais lire le fichier juste après la sortie de la commande, mais je ne veux pas lire un fichier vide.

Eric
la source
1
Il exécute probablement la commande immédiatement, mais le temps nécessaire pour ouvrir, écrire et fermer le fichier dépendra de la vitesse et du type de votre disque dur, des programmes en cours d'exécution, etc.
freginold
Dans l'exemple donné, qu'est-ce que «le processus»? Les processus sont echo- ils et >non séparés (de courte durée)? Et où la sortie de echorester avant >est-elle exécutée?
oɔɯǝɹ
1
@ oɔɯǝɹ >est la redirection de shell. C'est la même chose que si le programme avait ouvert le fichier nommé pour l'écriture et remplacé stdout par ce qui est exactement ce que fait le shell.
Dan D.
7
Je pense que c'est la responsabilité de l'OS de vous donner le filecontenant, Helloqu'il soit vidé ou non.
Salman A
1
Si le programme s'exécute sur la machine A et que vous lisez le fichier sur la machine B, avec le système de fichiers de la machine A monté sur le réseau, vous pouvez finir par lire un fichier vide, selon le type de système de fichiers réseau et les paramètres de montage. Vous souhaiterez donc peut-être désactiver la mise en cache pour ce montage.
pts

Réponses:

21

Plusieurs couches de tampons / caches sont impliquées.

  1. Le cache CPU.

    Les données sont regroupées octet par octet et stockées dans le cache CPU. Si le cache du processeur est plein et que les données n'ont pas été accessibles depuis un certain temps, le bloc contenant nos données peut être écrit dans la mémoire principale. Ceux-ci sont, pour la plupart, cachés aux programmeurs d'applications.

  2. Les tampons en cours.

    Il y a de la mémoire mise de côté dans le processus où les données sont collectées, nous devons donc faire le moins de requêtes possible au système d'exploitation, car c'est relativement cher. Le processus copie les données dans ces tampons, qui peuvent à nouveau être sauvegardés par des caches CPU, il n'y a donc aucune garantie que les données sont copiées dans la mémoire principale. L'application doit vider explicitement ces tampons, par exemple en utilisant fclose (3) ou fsync (3). La sortie (3) fonction fait aussi avant que le processus est terminé, alors que la fonction _exit (2) ne , ce qui explique pourquoi il y a un grand avertissement dans la page de manuel pour cette fonction de l' appeler seulement si vous savez ce que vous êtes Faire.

  3. Les tampons du noyau

    Le système d'exploitation conserve ensuite son propre cache, afin de minimiser le nombre de demandes qu'il doit envoyer aux disques. Ce cache n'appartient à aucun processus en particulier, donc les données qu'il contient peuvent appartenir à des processus qui sont déjà terminés, et puisque tous les accès passent par ici, le prochain programme verra les données s'il est arrivé ici. Le noyau écrira ces données sur les disques lorsqu'il aura le temps de le faire ou lorsque cela lui sera explicitement demandé.

  4. Le cache du lecteur

    Les disques durs eux-mêmes conservent également un cache pour accélérer les accès. Celles-ci sont écrites assez rapidement, et il existe une commande pour écrire les données restantes dans les caches et signaler quand cela est terminé, que le système d'exploitation utilise à l'arrêt pour s'assurer qu'aucune donnée n'est laissée non écrite avant la mise hors tension.

Pour votre application, il suffit que les données soient enregistrées dans les tampons du noyau (les données réelles peuvent encore vivre dans des caches CPU à ce stade, et peuvent ne pas avoir été écrites dans la mémoire principale): le processus "écho" se termine, ce qui signifie que tous les tampons en cours doivent avoir été vidés et que les données ont été transmises au système d'exploitation, et lorsque vous démarrez un nouveau processus, il est garanti que le système d'exploitation restituera les mêmes données à la demande.

Simon Richter
la source
7
Considérant que la mise en cache du CPU ne me semble pas pertinente. C'est un niveau de détail inutile ici. Comme en passant par tous les détails jusqu'à ce qu'une certaine quantité physique représentant un bit sur un plateau de disque dur ou une mémoire ssd soit modifiée pour le retourner.
mvw
3
En effet, le cache CPU est assez orthogonal.
Simon Richter
2
Et plus important encore, le cache du processeur est cohérent entre les cœurs, c'est pourquoi il est totalement hors de l'image. Sur x86, il est même cohérent avec DMA (et x86 a un mode de commande de mémoire d'ordre de stockage total), donc tout ce qui peut lire la mémoire verra les données les plus récemment stockées à cette adresse dans l'ordre global des opérations de mémoire. (Un cœur de processeur verra ses propres magasins avant même qu'ils ne deviennent globalement visibles, en raison du transfert de magasin à partir de la file d'attente du magasin). Sur les plates-formes non x86 sans DMA cohérent avec le cache, le noyau Linux s'assure que le cache est vidé avant le DMA vers ces adresses.
Peter Cordes
1
"Ceux-ci sont, pour la plupart, cachés aux programmeurs d'applications." Pourquoi le "pour la plupart"? Je suis un développeur intégré et sauf pendant le chargeur de démarrage (donc pas "application") j'ignore complètement le cache CPU. Je ne pense pas qu'un développeur d'applications puisse être affecté par les effets du cache CPU.
Sam
1
Les échecs / hits du cache @Sam ainsi que l'exécution spéculative peuvent être exploités dans certains processeurs pour contourner les restrictions d'accès en lecture. C'est peut-être à cela que la réponse fait référence?
John Dvorak
22

Si l'application n'a pas de caches internes, les modifications seront immédiatement écrites dans le fichier. De même pour votre exemple. Le fichier est une entité logique en mémoire qui sera immédiatement mise à jour. Toutes les opérations ultérieures sur le fichier verront les modifications apportées par le programme.

Toutefois , cela ne signifie pas que la modification a été écrite sur le disque physique. Les modifications peuvent persister dans les caches du système de fichiers du système d'exploitation ou les caches matériels. Pour vider les tampons du système de fichiers, utilisez la synccommande.

Je voudrais lire le fichier juste après la sortie de la commande, mais je ne veux pas lire un fichier vide.

Vous ne devriez pas rencontrer de problèmes pratiques ici.

mtak
la source
1
«Si l'application n'a pas de caches internes» - c'est un très gros «si»: la grande majorité des implémentations de bibliothèques d'E / S utilisent la mémoire tampon standard par défaut. Cela dit, la norme C par exemple exige que le tampon stdout soit vidé à la sortie (mais potentiellement pas s'il exitn'est pas au moins implicitement appelé). D'autres bibliothèques / langages (par exemple Java!) Offrent moins de garanties.
Konrad Rudolph
Et si la limiter à la primitive de redirection (c'est-à-dire la commande dans ma question)? Il n'a pas de caches internes, non?
Eric
@Eric Non, ça devrait aller.
mtak
10
Je ne sais pas si j'obtiens cette réponse. La question porte sur "la fin du processus". Chaque application avec des caches d'écriture internes les videra sur le disque à la sortie du processus, si cela ne s'est pas produit plus tôt. IOW, ces caches n'ont pas d'importance ici.
MSalters le
2
De plus, un tampon interne sera soit vidé à la sortie, soit s'effacera simplement de l'existance, non? Ainsi, même si les tampons internes ne sont pas vidés, le contenu ne sera pas observable, quel que soit le temps d'attente.
WorldSEnder
21

Le tampon sera-t-il automatiquement vidé sur le disque à la fin d'un processus?

En général, la réponse est non .

Cela dépend de la commande. Comme les autres réponses le mentionnent, si la commande ne met pas en mémoire tampon les données en interne, toutes les données seront disponibles à la fin de la commande.

Mais la plupart, sinon tous, les bibliothèques d' E / S standard ne stdout tampon par défaut (dans une certaine mesure), et donnent différentes garanties sur le rinçage automatique des tampons lorsque les applications de fermait ses portes.

C garantit qu'une sortie normale videra les tampons . «Sortie normale» signifie que l' exiton appelle - soit explicitement, soit en revenant de main. Cependant, une sortie anormale peut contourner cet appel (et donc laisser des tampons non vidés).

Voici un exemple simple:

#include <signal.h>
#include <stdio.h>

int main() {
    printf("test");
    raise(SIGABRT);
}

Si vous le compilez et l'exécutez, testil ne sera pas nécessairement écrit sur stdout.

D'autres langages de programmation offrent encore moins de garanties: Java, par exemple, ne se vide pas automatiquement à la fin du programme . Si le tampon de sortie contient une ligne non terminée, elle peut donc être perdue, sauf si elle a System.out.flush()été appelée explicitement.

Cela dit, votre corps de question demande quelque chose de légèrement différent: si les données arrivent dans le fichier du tout , il doit le faire immédiatement après la commande ( se termine sous réserve des mises en garde décrits dans les autres réponses).

Konrad Rudolph
la source
7
J'ai également vu une sortie anormale lorsqu'un outil de ligne de commande écrit dans un fichier et vers stdout ou stderr, comme un journal de débogage, et que l'utilisateur a créé un canal vers la tête ou moins, puis tapé `` q '' pour quitter moins. Le fichier disque n'est pas toujours entièrement vidé si l'outil de ligne de commande ne gère pas SIGPIPE.
Zan Lynx
+1, mais "il doit le faire immédiatement après la fin de la commande" n'est pas tout à fait correct: tout write()ou pwrite()appel système se produira avant la fin du processus, et c'est alors que les modifications du fichier deviennent visibles. Ainsi, le dernier changement de fichier est définitivement avant la fin du processus, immédiatement avant au plus tard. Je pense que même avec un mmap(MAP_SHARED)fichier, il n'y a aucun moyen d'observer la fin du processus avant que tous les changements de fichiers ne se produisent.
Peter Cordes
9

Je pense qu'aucune question n'aborde encore suffisamment cette question:

Je voudrais lire le fichier juste après la sortie de la commande, mais je ne veux pas lire un fichier vide.

Comme les autres réponses l'expliquent, un programme qui se comporte bien vide ses tampons de fichiers internes avant que le processus ne se termine normalement . Par la suite, les données peuvent encore persister dans les tampons du noyau ou du matériel avant d'être écrites sur un stockage persistant. Cependant , la sémantique du système de fichiers de Linux garantit que tous les processus voient le contenu des fichiers de la même manière que le noyau, y compris les tampons internes 1 .

Ceci est généralement implémenté en ayant au plus un tampon dans le noyau par objet fichier et en exigeant que tous les accès aux fichiers passent par ce tampon.

  • Si un processus lit un fichier, le noyau présentera le contenu du tampon au processus, si la partie de fichier demandée est actuellement dans le tampon; si ce n'est pas le cas, le noyau récupère les données du support de stockage sous-jacent et les place dans le tampon, puis revient à l'étape précédente.

  • Si un processus écrit dans un fichier, les données sont d'abord placées dans la mémoire tampon du noyau de ce fichier. Finalement, le contenu du tampon sera vidé dans le stockage. Dans l'intervalle, l'accès en lecture est satisfait à partir du même tampon (voir ci-dessus).


1 Au moins pour les fichiers, répertoires et liens symboliques normaux. Les FIFO et les sockets sont une question différente car leur contenu n'est jamais stocké de manière persistante de toute façon. Il existe des cas particuliers de fichiers normaux dont le contenu dépend de qui demande; des exemples sont des fichiers dans procfs et sysfs (pensez /proc/selfqui est un lien symbolique vers l'ID de processus du processus lisant le lien symbolique).

David Foerster
la source
2
À strictement parler, ce n'est pas la sémantique du système de fichiers de Linux qui le garantit, ce sont les sémantiques POSIX qui le font. En particulier, BSD se comporte exactement de la même manière que macOS et même Windows (bien que ce soit l'un des rares cas où Windows suit la sémantique POSIX). Cela suppose également que personne ne fait des choses étranges avec mmap()et O_DIRECT, ce qui peut entraîner une désynchronisation entre le disque et le cache de pages (mais cela résoudra le moment où le processus qui se termine).
Austin Hemmelgarn
2
@AustinHemmelgarn: Strictement parlant, nous avons tous les deux raison depuis que Linux a été conçu avec le support des applications Unix (System V) à l'esprit et plus tard conçu pour prendre en charge POSIX qui base également de nombreux concepts sur System V.
David Foerster
5

En supposant que votre commande est exécutée par un programme utilisant la bibliothèque d'exécution C, à un moment donné, il devrait invoquer fclosepour fermer le fichier ouvert.

La page de manuel de la fclosefonction C indique:

NOTES Notez que fclose () vide uniquement les tampons d'espace utilisateur fournis par la bibliothèque C. Pour garantir que les données sont physiquement stockées sur le disque, les tampons du noyau doivent également être vidés, par exemple, avec sync (2) ou fsync (2).

et la page de manuel pour fflusha la même note. La page de manuel de closedit:

Une fermeture réussie ne garantit pas que les données ont bien été enregistrées sur le disque, comme l'écrit le noyau diffère. Il n'est pas courant qu'un système de fichiers vide les tampons lorsque le flux est fermé. Si vous devez être sûr que les données sont stockées physiquement, utilisez fsync (2). (Cela dépendra du matériel du disque à ce stade.)

Notez que les données sont disponibles pour d'autres processus même si elles ne sont pas synchronisées avec le lecteur. C'est peut-être déjà assez bon pour vous.

En cas de doute, faites un test.

mvw
la source
2
C ou pas, tout va / devrait utiliser l'appel close()système pour fermer le descripteur d'un fichier.
Attie du
@Attie: Vous n'avez pas besoin de closefichiers avant de quitter (dans les programmes hacky qui ne vérifient pas les erreurs); le noyau les nettoiera, vous appelant efficacement closeaprès la fin de votre processus. Vous avez cependant besoin de fclosetous les flux stdio tamponnés, ou laissez libc le faire pour vous exit(3), contrairement à l'appel système de sortie directement.
Peter Cordes
En cas de doute, faites un test. C'est un mauvais conseil pour détecter les conditions de course. Les tests sur un noyau exécuté sur un seul matériel peuvent vous dire que la course ne peut pas se produire dans les conditions logicielles produites par votre test sur ce système, ou si c'est le cas, c'est trop rare pour être détecté. Mais il ne peut pas vous dire si ce comportement est censé être sûr sur tous les systèmes de fichiers, noyaux et tout le matériel (par exemple PowerPC). c'est-à-dire que vous ne pouvez pas dire si la garantie dont vous dépendez est un détail d'implémentation ou une garantie intentionnelle à l'épreuve du temps! (Dans ce cas, c'est le cas.)
Peter Cordes
Ça dépend de la situation. Certaines personnes essayant de faire fonctionner son script shell pourraient être aidées par ces conseils. Il n'était pas destiné à être une solution générale pour les environnements plus avancés mais moins probables, par exemple un ingénieur logiciel travaillant sur un noyau de système d'exploitation, des gens travaillant sur la mise à jour du microcode d'Intel, ou une fille travaillant sur un système pour l'ISS.
mvw
3

Lorsque je redirige la sortie d'une commande vers un fichier (par exemple, echo Hello > file) ce fichier sera-t-il garanti d'avoir de telles données juste après la sortie de la commande?

Oui. Le shell ouvre le fichier de echosortie et y renvoie directement. Une fois la commande terminée, c'est fait.

Ou existe-t-il encore une très petite fenêtre entre les sorties de commande et les données écrites dans le fichier?

Que les données soient déjà sur le support est une autre question, qui n'a d'importance que s'il y a par la suite une défaillance matérielle, ou si vous inspectez la partition en direct avec un logiciel judiciaire, en contournant le système de fichiers monté.

Je voudrais lire le fichier juste après la sortie de la commande, mais je ne veux pas lire un fichier vide.

Ne vous inquiétez pas, le noyau ne conserve qu'une seule vue du fichier, quelle que soit la fréquence d'ouverture.

Déduplicateur
la source
"le noyau ne conserve qu'une seule vue du fichier": pas tout à fait vrai pour mmap(MAP_SHARED): les magasins dans la région mmapée ne sont pas cohérents avec les lectures du fichier (par ce thread ou d'autres processus). Voilà pourquoi msync(2)existe. C'est du moins ce que préviennent les pages de manuel; selon l'implémentation, Linux peut en fait mapper des pages physiques du cache de pages, auquel cas je suppose qu'il est fondamentalement cohérent (ordonnancement de la mémoire modulo). Quoi qu'il en soit, tout se passe toujours avant _exit(2).
Peter Cordes
2

En règle générale, toutes les données appartenant au noyau sont conservées et nettoyées par le noyau, point final. Ces données incluent les données transférées dans la mémoire du noyau par un appel système tel que write(2).

Cependant, si votre application (par exemple la bibliothèque C) effectue une mise en mémoire tampon en plus de cela, le noyau n'a évidemment aucune idée et ne garantit donc pas son nettoyage.

De plus, je ne crois pas qu'il y ait une garantie de calendrier pour le nettoyage - il est, en général, effectué sur la base du "meilleur effort" (lire: "quand j'ai une seconde").

Mehrdad
la source
Il y a une garantie que tout nettoyage / vidage de tampon se produira avant le waitpid()retour d' un processus parent , si le nettoyage a lieu. c'est-à-dire que les autres processus ne peuvent pas observer directement l' arrêt du processus avant toute modification de fichier effectuée par ce processus. (J'ai dit "directement" pour exclure l'observation indirecte via les horodatages des fichiers NFS, car la mise en cache NFS n'est pas parfaitement cohérente entre les hôtes.)
Peter Cordes
@PeterCordes: Je suppose que cela dépend de ce que vous entendez par "nettoyage" par opposition à "maintenir". Pour moi, "maintenir", c'est "fournir une vue cohérente" (qui a la garantie que vous avez mentionnée) et "nettoyer", c'est "vider le disque" qui, je ne crois pas, a une garantie de timing.
Mehrdad
Oh, je vois, vous répondez à la partie "vidée sur le disque" de la question qui est sans rapport avec ce que les processus ultérieurs verront lors de la lecture du fichier. "nettoyer" dans le sens de "nettoyer la mémoire cache / tampon sale des E / S". À droite, aucune garantie de synchronisation à moins que vous n'utilisiez fsync/ fdatasync, bien que la réécriture de la mémoire tampon sur Linux démarre après des /proc/sys/vm/dirty_writeback_centisecscentièmes de seconde (si elle n'est pas retardée par un autre trafic d'E / S), et divers autres paramètres ajustables dans ce répertoire procfs affectent également les choses (par exemple, comment large pour laisser les tampons croître avant de faire une réécriture).
Peter Cordes
2

Ou existe-t-il encore une très petite fenêtre entre les sorties de commande et les données écrites dans le fichier?

Non, il n'y en a pas.

Je voudrais lire le fichier juste après la sortie de la commande, mais je ne veux pas lire un fichier vide.

Vous pouvez lire le contenu final du fichier juste après la fin de la commande, vous ne lirez jamais le fichier vide à la place. (En C et C ++, utilisez les appels système wait , waitpid , wait3 ou wait4 pour attendre la fin du programme, puis lisez le fichier uniquement. Si vous utilisez un shell, un autre langage de programmation ou une bibliothèque (par exemple la bibliothèque C système d' appel ou la classe Java Process ), il utilise probablement déjà l'un de ces appels système.)

Comme d'autres réponses et commentaires l'ont souligné, vous pouvez finir par lire un fichier vide après la sortie du programme si le programme est sorti sans vider ses tampons de sortie internes (par exemple à cause de _exit , abandonner ou recevoir un signal fatal, ou parce qu'il est un programme Java se fermant normalement). Cependant, vous ne pouvez rien faire à ce stade: les données non vidées sont perdues à jamais, une attente supplémentaire ne les récupérera pas.

pts
la source
0

Oui

Désolé d'avoir peut-être ajouté une autre réponse superflue, mais la plupart semblent se concentrer sur le hareng rouge du titre de la question. Mais pour autant que je sache, la question ne concerne pas du tout la mise en mémoire tampon, mais ceci:

Lorsque je redirige la sortie d'une commande vers un fichier (par exemple, echo Hello> fichier), ce fichier sera-t-il garanti d'avoir de telles données juste après la fin de la commande?

Oui, sans condition. L'utilisation de ">" que vous décrivez, avec "|" et "<", est le modèle de traitement basé sur les canaux sur lequel le monde Unix et Linux est fortement basé. Vous trouverez des centaines, voire des milliers de scripts dépendant totalement de ce comportement dans chaque installation Linux.

Il fonctionne comme vous le souhaitez par conception, et s'il y avait même la moindre chance d'une condition de course, elle aurait été corrigée il y a probablement des décennies.

AnoE
la source
C'est superflu, malheureusement. Seules quelques-unes des réponses se concentrent principalement sur le redingue de la validation des données dans un stockage non volatile. Voir la réponse de @ pts et plusieurs autres pour une description claire: la modification du fichier se produit avant la sortie, ou pas du tout.
Peter Cordes