anomalia printf dopo fork()

anomalia printf dopo fork()


Sistema operativo:Linux, Linguaggio:C puro


Sto andando avanti nell'apprendimento della programmazione C in generale e della programmazione C in UNIX in un caso speciale.


Ho rilevato uno strano (per me) comportamento del printf() funzione dopo aver utilizzato un fork() chiamata.


Codice


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

Risultato


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

Perché la seconda stringa "Hello" si è verificata nell'output del figlio?


Sì, è esattamente ciò che il genitore ha stampato all'inizio, con il pid del genitore .


Ma! Se inseriamo un \n carattere alla fine di ogni stringa otteniamo l'output atteso:


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

Risultato :


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

Perché succede? È un comportamento corretto o è un bug?


Risposte:


Prendo atto che <system.h> è un'intestazione non standard; L'ho sostituito con <unistd.h> e il codice compilato in modo pulito.


Quando l'output del tuo programma sta andando su un terminale (schermo), viene bufferizzato in linea. Quando l'output del tuo programma va in una pipe, è completamente memorizzato nel buffer. Puoi controllare la modalità di buffering tramite la funzione C standard setvbuf() e il _IOFBF (bufferizzazione completa), _IOLBF (bufferizzazione di riga) e _IONBF (senza buffering).


Potresti dimostrarlo nel tuo programma rivisto reindirizzando l'output del tuo programma, ad esempio, a cat . Anche con le nuove righe alla fine del printf() stringhe, vedresti la doppia informazione. Se lo invii direttamente al terminale, vedrai solo un sacco di informazioni.


La morale della storia è fare attenzione a chiamare fflush(0); per svuotare tutti i buffer di I/O prima del fork.



Analisi riga per riga, come richiesto (parentesi ecc. rimosse - e spazi iniziali rimossi dall'editor di markup):



  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'analisi:



  1. Copia "Hello, my pid is 1234" nel buffer per l'output standard. Poiché non c'è una nuova riga alla fine e l'output è in esecuzione in modalità con buffer di linea (o modalità con buffer completo), sul terminale non viene visualizzato nulla.

  2. Ci offre due processi separati, con esattamente lo stesso materiale nel buffer stdout.

  3. Il bambino ha pid == 0 ed esegue le righe 4 e 5; il genitore ha un valore diverso da zero per pid (una delle poche differenze tra i due processi:valori di ritorno da getpid() e getppid() sono altri due).

  4. Aggiunge una nuova riga e "Sono stato biforcato! :D" al buffer di output del figlio. La prima riga di output appare sul terminale; il resto è trattenuto nel buffer poiché l'output è bufferizzato in linea.

  5. Tutto si ferma per 3 secondi. Dopo questo, il bambino esce normalmente attraverso il ritorno alla fine del main. A quel punto, i dati residui nel buffer stdout vengono cancellati. Questo lascia la posizione di output alla fine di una riga poiché non c'è una nuova riga.

  6. Il genitore viene qui.

  7. Il genitore aspetta che il bambino finisca di morire.

  8. Il genitore aggiunge una nuova riga e "1345 è stato biforcato!" al buffer di uscita. La nuova riga scarica il messaggio "Ciao" nell'output, dopo la riga incompleta generata dal bambino.


Il genitore esce ora normalmente attraverso il ritorno alla fine del main e i dati residui vengono cancellati; poiché non c'è ancora una nuova riga alla fine, la posizione del cursore è dopo il punto esclamativo e il prompt della shell appare sulla stessa riga.


Quello che vedo è:


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:

I numeri PID sono diversi, ma l'aspetto generale è chiaro. Aggiunta di nuove righe alla fine del printf() le istruzioni (che diventano una pratica standard molto rapidamente) alterano molto l'output:


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

Ora ottengo:


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:

Si noti che quando l'output va al terminale, viene bufferizzato in linea, quindi la riga "Hello" viene visualizzata prima del fork() e c'era solo una copia. Quando l'output viene reindirizzato a cat , è completamente bufferizzato, quindi non viene visualizzato nulla prima del fork() ed entrambi i processi hanno la riga "Hello" nel buffer da svuotare.