fork() 後の printf 異常



OS:Linux、言語:pure C


私は C プログラミング全般の学習を進めており、特別なケースでは UNIX での C プログラミングを学習しています。


printf() の (私にとって) 奇妙な動作を検出しました fork() を使用した後の関数 電話。


コード


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

出力


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

子の出力で 2 番目の「Hello」文字列が発生したのはなぜですか?


はい、親の pid を使用して、親が開始時に印刷したものとまったく同じです .


しかし! \n を配置すると 各文字列の末尾に文字を追加すると、期待される出力が得られます:


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

出力 :


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

なぜそれが起こるのですか?それは正しい動作ですか、それともバグですか?


答え:


<system.h> に注意してください 非標準ヘッダーです。 <unistd.h> に置き換えました そしてコードはきれいにコンパイルされました。


プログラムの出力が端末 (画面) に送られるときは、ライン バッファリングされます。プログラムの出力がパイプに送られると、完全にバッファリングされます。標準 C 関数 setvbuf() でバッファリング モードを制御できます。 そして _IOFBF (完全なバッファリング)、_IOLBF (行バッファリング) および _IONBF (バッファリングなし) モード。


プログラムの出力を cat などにパイプすることで、修正したプログラムでこれを実証できます。 . printf() の末尾に改行があっても 文字列の場合、二重の情報が表示されます。端末に直接送信すると、1 つの情報だけが表示されます。


この話の教訓は、fflush(0); を呼び出すのに注意することです。 フォークする前にすべての I/O バッファを空にします。



要求に応じて行ごとの分析 (中括弧などを削除 - マークアップ エディターによって先頭のスペースを削除):



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


分析:



  1. 「こんにちは、私の pid は 1234 です」を標準出力用のバッファにコピーします。最後に改行がなく、出力が行バッファ モード (またはフル バッファ モード) で実行されているため、端末には何も表示されません。

  2. stdout バッファ内のまったく同じマテリアルを使用して、2 つの別個のプロセスを提供します。

  3. 子供は pid == 0 を持っています 4 行目と 5 行目を実行します。親の pid はゼロ以外の値です (2 つのプロセスの数少ない違いの 1 つ - getpid() からの戻り値 と getppid() はあと 2 つです)。

  4. 改行と「I was forked! :D」を子の出力バッファに追加します。出力の最初の行が端末に表示されます。出力は行バッファリングされるため、残りはバッファに保持されます。

  5. すべてが 3 秒間停止します。この後、子は main の最後の return で正常に終了します。その時点で、stdout バッファー内の残りのデータがフラッシュされます。改行がないため、出力位置は行末のままになります。

  6. 親がここに来る

  7. 親は子供が死に終わるのを待ちます。

  8. 親は改行を追加し、「1345 がフォークされました!」出力バッファに。改行は、子によって生成された不完全な行の後に、'Hello' メッセージを出力にフラッシュします。


親は main の最後に戻ることで正常に終了し、残りのデータはフラッシュされます。最後にまだ改行がないため、カーソル位置は感嘆符の後にあり、シェル プロンプトは同じ行に表示されます。


私が見ているもの:


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:

PID の数値は異なりますが、全体的な外観は明らかです。 printf() の末尾に改行を追加する ステートメント (これは非常にすぐに標準的なプラクティスになります) は、出力を大幅に変更します:


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

私は今得ます:


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:

出力が端末に送られると、行バッファリングされるため、「Hello」行が fork() の前に表示されることに注意してください。 コピーは1つだけでした。出力が cat にパイプされる場合 、完全にバッファリングされているため、 fork() の前には何も表示されません 両方のプロセスのバッファーに、フラッシュされる「Hello」行があります。