93 votes

printf anomalie après "fork()"

Système d'exploitation: Linux, Langue: pur C

Je suis aller de l'avant dans l'apprentissage du C progpramming en général, et de la programmation en C sous UNIX dans un cas particulier :D Donc, j'ai détecté un étrange (comme pour moi) le comportement de la fonction printf() après à l'aide d'un appel fork (). Jetons un coup d'oeil au programme de test simple:

#include <stdio.h>
#include <system.h>

int main()
{
    int pid;
    printf( "Hello, my pid is %d", getpid() );

    pid = fork();
    if( pid == 0 )
    {
            printf( "\nI was forked! :D" );
            sleep( 3 );
    }
    else
    {
            waitpid( pid, NULL, 0 );
            printf( "\n%d was forked!", pid );
    }
    return 0;
}

Dans ce cas, la sortie ressemble à ceci:

Hello, my pid is 1111
I was forked! :DHello, my pid is 1111
2222 was forked!

Pourquoi la deuxième chaîne "Hello" s'est produite dans l'enfant de la sortie? Oui, c'est exactement ce que le parent imprimé sur démarrer, avec le parent pid.

Mais! Si l'on place '\n' dans la fin de chaque chaîne, nous avons obtenu le résultat attendu:

#include <stdio.h>
#include <system.h>

int main()
{
    int pid;
    printf( "Hello, my pid is %d\n", getpid() ); // SIC!!

    pid = fork();
    if( pid == 0 )
    {
            printf( "I was forked! :D" ); //removed the '\n', no matter
            sleep( 3 );
    }
    else
    {
            waitpid( pid, NULL, 0 );
            printf( "\n%d was forked!", pid );
    }
    return 0;
}

Et la sortie ressemble à ceci:

Hello, my pid is 1111
I was forked! :D
2222 was forked!

Pourquoi se produit-il? Est-il ... ummm ... bon comportement? Ou c'est une sorte de "bug'?

102voto

Jonathan Leffler Points 299946

Je note que l' <system.h> est un non-standard-tête; je l'ai remplacé par <unistd.h> et le code compilé correctement.

Lors de la sortie de votre programme est d'aller dans un terminal (écran), c'est la ligne de mise en mémoire tampon. Lors de la sortie de votre programme va à un tuyau, il est entièrement mis en mémoire tampon. Vous pouvez contrôler le mode de mise en mémoire tampon par le Standard de la fonction C setvbuf() et de la _IOFBF (complet) la mémoire tampon, _IOLBF (ligne) la mémoire tampon et d' _IONBF (pas de tampon) modes de fonctionnement.

Vous avez pu le démontrer dans votre programme révisé par la tuyauterie de la sortie de votre programme, par exemple, cat. Même avec les retours à la ligne à la fin de l' printf() des chaînes, vous pouvez voir la double information. Si vous l'envoyez directement à la borne, puis vous verrez un grand nombre d'informations.

La morale de l'histoire est prudent de faire appel fflush(0); de vider tous les tampons d'e/S avant de bifurquer.


Ligne-par-ligne d'analyse, comme l'a demandé (broches etc supprimés et les espaces enlevé par un marquage de l'éditeur):

  1. printf( "Hello, my pid is %d", getpid() );
  2. pid = fork();
  3. if( pid == 0 )
  4. printf( "\nI was forked! :D" );
  5. sleep( 3 );
  6. else
  7. waitpid( pid, NULL, 0 );
  8. printf( "\n%d was forked!", pid );

L'analyse:

  1. Des Copies "Bonjour, mon pid est 1234" dans le tampon de sortie standard. Car il n'y a pas de saut de ligne à la fin et la sortie est en cours d'exécution en ligne-mode mémoire tampon (ou full-mode mémoire tampon), rien ne s'affiche sur le terminal.
  2. Donne-nous de deux processus distincts, avec exactement le même matériel dans la sortie standard (stdout) de la mémoire tampon.
  3. L'enfant a pid == 0 et exécute les lignes 4 et 5; le parent a une valeur non nulle pour pid (l'un des rares différences entre les deux processus - les valeurs de retour de getpid() et getppid() sont les deux autres).
  4. Ajoute un saut de ligne et "j'ai été fourchue! :D" à la mémoire tampon de sortie de l'enfant. La première ligne de sortie s'affiche sur le terminal; le reste est tenue dans la mémoire tampon depuis la sortie est de la ligne de mise en mémoire tampon.
  5. Tout s'arrête pendant 3 secondes. Après cela, l'enfant s'arrête normalement à travers le retour à la fin de la main. À ce stade, les données résiduelles dans la sortie standard (stdout) de la mémoire tampon est vidé. Cela laisse de la position de sortie à la fin d'une ligne car il n'y a pas de saut de ligne.
  6. Le parent vient ici.
  7. Le parent attend l'enfant à la fin de mourir.
  8. La mère ajoute un saut de ligne et "1345 était fourchue!" à la mémoire tampon de sortie. Le saut de ligne vide le "Bonjour" message à la sortie, après le incomplète généré par l'enfant.

Le parent maintenant arrête normalement à travers le retour à la fin de la main, et les données résiduelles est vidé; car il n'y a pas de saut de ligne à la fin, la position du curseur après le point d'exclamation, et l'invite du shell s'affiche sur la même ligne.

Ce que je vois, c'est:

Osiris-2 JL: ./xx
Hello, my pid is 37290
I was forked! :DHello, my pid is 37290
37291 was forked!Osiris-2 JL: 
Osiris-2 JL: 

Le PID numéros sont différents, mais l'aspect général est clair. L'ajout de nouvelles lignes à la fin de l' printf() des déclarations (qui devient la norme de pratique très rapidement) modifie la sortie d'un lot:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int pid;
    printf( "Hello, my pid is %d\n", getpid() );

    pid = fork();
    if( pid == 0 )
        printf( "I was forked! :D %d\n", getpid() );
    else
    {
        waitpid( pid, NULL, 0 );
        printf( "%d was forked!\n", pid );
    }
    return 0;
}

Maintenant, je reçois:

Osiris-2 JL: ./xx
Hello, my pid is 37589
I was forked! :D 37590
37590 was forked!
Osiris-2 JL: ./xx | cat
Hello, my pid is 37594
I was forked! :D 37596
Hello, my pid is 37594
37596 was forked!
Osiris-2 JL:

Notez que lors de la sortie passe à la borne, c'est la ligne de tampon, de sorte que le "Bonjour" de la ligne s'affiche avant l' fork() et il n'y avait qu'un seul exemplaire. Lorsque la sortie est redirigée vers cat, il est pleinement mis en mémoire tampon, de sorte que rien ne s'affiche avant l' fork() , et les deux procédés ont le "Bonjour" de la ligne dans la mémoire tampon pour être vidées.

27voto

JaredPar Points 333733

La raison en est que, sans l' \n à la fin de la chaîne de format de la valeur n'est pas imprimée immédiatement à l'écran. Au lieu de cela, il est mis en mémoire tampon dans le processus. Cela signifie qu'il n'est pas en cours d'impression jusqu'à ce que après la fourche opération donc vous le faire imprimer deux fois.

L'ajout de l' \n bien que les forces de la mémoire tampon pour être rincé et affiché à l'écran. Ce qui se passe avant de la fourche, et d'où est imprimé uniquement une fois.

Vous pouvez forcer cette de se produire à l'aide de l' fflush méthode. Par exemple

printf( "Hello, my pid is %d", getpid() );
fflush(stdout);

6voto

mark4o Points 20472

fork() crée effectivement une copie de la procédure. Si, avant d'appeler fork(), il avait des données qui a été mise en mémoire tampon, à la fois le parent et l'enfant ont le même tampon de données. La prochaine fois que chacun d'eux fait quelque chose pour vider sa mémoire tampon (telles que l'impression d'un retour à la ligne dans le cas de la sortie du terminal), vous verrez que le tampon de sortie, en plus de toute nouvelle sortie produite par ce processus. Donc, si vous allez utiliser stdio à la fois le parent et l'enfant, alors vous devriez fflush avant de bifurquer, pour s'assurer qu'il n'existe pas de données mises en mémoire tampon.

Souvent, l'enfant est utilisé uniquement pour appeler un exec* fonction. Depuis des qui remplace la complète enfant image de processus (y compris les mémoires tampons) il n'y a techniquement pas besoin d' fflush si c'est vraiment tout ce que vous allez faire à l'enfant. Toutefois, s'il peut être mis en mémoire tampon de données, alors vous devez être prudent dans la façon dont un exec échec est manipulé. En particulier, d'éviter l'impression de l'erreur sur la sortie standard stdout ou stderr à l'aide de tout stdio fonction (write est ok), puis appelez _exit (ou _Exit) plutôt que d'appeler exit ou tout simplement rentrer (ce qui permettra d'éliminer toute tampon de sortie). Ou éviter le problème carrément de le rincer avant de bifurquer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X