printf-Anomalie nach fork()

printf-Anomalie nach fork()


Betriebssystem:Linux, Sprache:reines C


Ich mache Fortschritte beim Erlernen der C-Programmierung im Allgemeinen und der C-Programmierung unter UNIX in einem speziellen Fall.


Ich habe ein (für mich) seltsames Verhalten von printf() festgestellt Funktion nach Verwendung einer fork() Anruf.


Code


#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;
}

Ausgabe


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

Warum tauchte die zweite „Hello“-Zeichenfolge in der Ausgabe des untergeordneten Elements auf?


Ja, es ist genau das, was der Elternteil zu Beginn gedruckt hat, mit dem pid des Elternteils .


Aber! Wenn wir eine \n platzieren Zeichen am Ende jeder Zeichenfolge erhalten wir die erwartete Ausgabe:


#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;
}

Ausgabe :


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

Warum passiert das? Ist das Verhalten korrekt oder handelt es sich um einen Fehler?


Antworten:


Ich bemerke, dass <system.h> ist ein nicht standardmäßiger Header; Ich habe es durch <unistd.h> ersetzt und der Code sauber kompiliert.


Wenn die Ausgabe Ihres Programms an ein Terminal (Bildschirm) geht, wird sie zeilengepuffert. Wenn die Ausgabe Ihres Programms an eine Pipe geht, wird sie vollständig gepuffert. Sie können den Puffermodus durch die Standard-C-Funktion setvbuf() steuern und die _IOFBF (volle Pufferung), _IOLBF (Zeilenpufferung) und _IONBF (keine Pufferung) Modi.


Sie könnten dies in Ihrem überarbeiteten Programm demonstrieren, indem Sie die Ausgabe Ihres Programms beispielsweise an cat weiterleiten . Sogar mit den Zeilenumbrüchen am Ende des printf() Zeichenfolgen, würden Sie die doppelte Information sehen. Wenn Sie es direkt an das Terminal senden, sehen Sie nur eine Menge Informationen.


Die Moral der Geschichte ist, darauf zu achten, fflush(0); anzurufen um alle E/A-Puffer vor dem Forken zu leeren.



Zeile-für-Zeile-Analyse, wie gewünscht (geschweifte Klammern usw. entfernt - und führende Leerzeichen durch Markup-Editor entfernt):



  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 );


Die Analyse:



  1. Kopiert "Hallo, meine PID ist 1234" in den Puffer für die Standardausgabe. Da am Ende kein Zeilenumbruch steht und die Ausgabe im Line-Buffered-Modus (bzw. Full-Buffered-Modus) läuft, erscheint nichts auf dem Terminal.

  2. Ergibt zwei getrennte Prozesse mit genau demselben Material im stdout-Puffer.

  3. Das Kind hat pid == 0 und führt die Zeilen 4 und 5 aus; das übergeordnete Element hat einen Wert ungleich Null für pid (einer der wenigen Unterschiede zwischen den beiden Prozessen - Rückgabewerte von getpid() und getppid() sind noch zwei).

  4. Fügt einen Zeilenumbruch und "I was forked! :D" zum Ausgabepuffer des Kindes hinzu. Die erste Ausgabezeile erscheint auf dem Terminal; der Rest wird im Puffer gehalten, da die Ausgabe zeilengepuffert ist.

  5. Alles hält für 3 Sekunden an. Danach verlässt das Kind normal durch die Rückkehr am Ende von main. An diesem Punkt werden die Restdaten im stdout-Puffer geleert. Dadurch bleibt die Ausgabeposition am Ende einer Zeile, da es keinen Zeilenumbruch gibt.

  6. Die Eltern kommen hierher.

  7. Die Eltern warten darauf, dass das Kind stirbt.

  8. Der Elternteil fügt einen Zeilenumbruch hinzu und "1345 wurde gegabelt!" zum Ausgangspuffer. Der Zeilenumbruch spült die 'Hallo'-Nachricht nach der vom Kind erzeugten unvollständigen Zeile in die Ausgabe.


Der Elternteil wird nun normal durch die Rückkehr am Ende von main beendet, und die Restdaten werden geleert; Da am Ende immer noch kein Zeilenumbruch steht, befindet sich die Cursorposition hinter dem Ausrufezeichen, und der Shell-Prompt erscheint in derselben Zeile.


Was ich sehe ist:


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:

Die PID-Nummern sind unterschiedlich - aber das Gesamtbild ist klar. Hinzufügen von Zeilenumbrüchen am Ende von printf() -Anweisungen (die sehr schnell zur Standardpraxis werden) verändert die Ausgabe stark:


#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;
}

Ich bekomme jetzt:


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:

Beachten Sie, dass die Ausgabe, wenn sie an das Terminal geht, zeilengepuffert ist, sodass die Zeile „Hello“ vor dem fork() erscheint und es gab nur das eine Exemplar. Wenn die Ausgabe an cat geleitet wird , es ist vollständig gepuffert, sodass nichts vor fork() erscheint und beide Prozesse haben die 'Hello'-Zeile im zu leerenden Puffer.