La sortie du programme change lorsqu’il est canalisé

J’ai un programme simple C pour démarrer le processus de temps (je préfère ne pas publier le code complet car il s’agit d’une affectation scolaire active). Ma fonction principale ressemble à ceci:

int main(void) { int i; for (i = 0; i < 5; i++) { printf("%lf\n", sample_time()); } exit(0); } 

sample_time() est une fonction qui sample_time() temps nécessaire pour générer un nouveau processus et renvoie le résultat en secondes sous forme de double . La partie de sample_time() qui forge:

 double sample_time() { // timing stuff if (!fork()) exit(0); // immediately close new process // timing stuff return a_sample_time; } 

Comme prévu, l’exécution du programme, times , dans le terminal génère 5 numéros comme suit:

 $ ./times 0.000085 0.000075 0.000079 0.000071 0.000078 

Toutefois, si vous essayez de transférer ce fichier dans un fichier (ou ailleurs) dans un terminal Unix, vous obtenez des résultats inattendus.

Par exemple, ./times > times.out crée un fichier avec quinze numéros. De plus, ./times | wc -l ./times | wc -l sorties 15 , confirmant le résultat précédent. En ./times | cat exécution ./times | cat ./times | cat , je vois encore quinze numéros, dont plus de cinq sont distincts .

Est-ce que quelqu’un sait ce qui peut causer quelque chose comme ça? Je suis à court d’idées.

./times ! = ./times | cat ./times | cat Wat.

Connaissances préalables

  • Fait 1 – Lorsque stdout est connecté à un TTY, il est mis en ligne. Lorsqu’il est connecté à un fichier ou à un pipeline, il est entièrement tamponné. Cela signifie qu’il n’a été extrait que par 8 Ko , plutôt que par ligne.

  • Fait 2 – Les processus fourchus ont des copies en double des données en mémoire. Cela inclut les tampons de sortie de stdio si les données n’ont pas encore été vidées.

  • Fait 3 – L’appel de exit() provoque le vidage des tampons de sortie du stdio avant la fermeture du programme.

Cas 1: Sortie vers le terminal

Lorsque votre programme imprime sur le terminal, sa sortie est mise en tampon de ligne. Chaque appel à printf() qui se termine par \n imprime immédiatement. Cela signifie que chaque ligne est imprimée et que le tampon de sortie en mémoire est vidé avant l’exécution de fork() .

Résultat : 5 lignes de sortie.

Cas 2: Sortie dans un pipeline ou un fichier

Lorsque libc voit que stdout n’est pas connecté à un téléscripteur, il passe à une stratégie de mise en mémoire tampon complète plus efficace. Cela provoque la mise en mémoire tampon de la sortie jusqu’à ce que la valeur de 4 Ko ait été accumulée. Cela signifie que la sortie du printf() s est enregistrée en mémoire et que les appels à write() sont différés.

 if (!fork()) exit(0); 

Après avoir été falsifié, le processus fils a une copie de la sortie mise en mémoire tampon. L’appel exit() provoque alors le vidage de ce tampon. Cela n’affecte pas le processus parent, cependant. Sa sortie est toujours tamponnée .

Ensuite, lorsque la deuxième ligne de sortie est imprimée, deux lignes sont mises en mémoire tampon. Le processus enfant suivant crée, quitte et imprime ces deux lignes. Le parent conserve ses deux lignes de sortie, et ainsi de suite.

Résultat: le processus enfant imprime 0, 1, 2, 3 et 4 lignes de sortie. Le programme principal imprime 5 quand il sort et vide sa sortie. 0 + 1 + 2 + 3 + 4 + 5 = 15. 15 lignes de sortie au lieu de 5!

Solutions

  1. Appelez _Exit() au lieu de exit() . La fonction _Exit() est comme exit() , mais n’appelle aucune fonction enregistrée avec atexit() . Ce serait ma solution préférée.

  2. setvbuf(stdout, NULL, _IOLBF, 0); standard comme étant mise en ligne: setvbuf(stdout, NULL, _IOLBF, 0);

  3. Appelez fflush(stdout) après chaque printf .