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つの情報だけが表示されます。
話の教訓は、フォークする前にすべてのI/Oバッファーを空にするためにfflush(0);
を呼び出すように注意することです。
要求された行ごとの分析(ブレースなどが削除され、先頭のスペースがマークアップエディターによって削除されました):
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 );
解析:
pid == 0
_を持ち、4行目と5行目を実行します。親にはpid
のゼロ以外の値があります(2つのプロセスのわずかな違いの1つ-getpid()
とgetppid()
からの戻り値はさらに2つです)。これで、親は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:
_
出力が端末に送信されると、行バッファリングされるため、fork()
の前に 'Hello'行が表示され、コピーが1つしかなかったことに注意してください。出力がcat
にパイプされると、完全にバッファリングされるため、fork()
の前には何も表示されず、両方のプロセスには、バッファに「Hello」行がフラッシュされます。
理由は、書式文字列の最後に\n
がないと、値がすぐに画面に出力されないためです。代わりに、プロセス内でバッファリングされます。これは、フォーク操作の後まで実際に印刷されないため、2回印刷されることを意味します。
ただし、\n
を追加すると、バッファが強制的にフラッシュされ、画面に出力されます。これはフォークの前に発生するため、一度だけ印刷されます。
fflush
メソッドを使用して、これを強制的に発生させることができます。例えば
printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
fork()
は、プロセスのコピーを効果的に作成します。 fork()
を呼び出す前に、バッファリングされたデータがある場合、親と子の両方が同じバッファリングされたデータを持ちます。次に、それぞれがそのバッファーをフラッシュするために何かを実行すると(端末出力の場合に改行を印刷するなど)、そのプロセスによって生成される新しい出力に加えて、そのバッファー出力が表示されます。したがって、親と子の両方でstdioを使用する場合は、フォークする前にfflush
を使用して、バッファリングされたデータがないことを確認する必要があります。
多くの場合、子は_exec*
_関数を呼び出すためにのみ使用されます。それは完全な子プロセスイメージ(バッファを含む)を置き換えるので、実際にそれが子で行うすべてである場合、fflush
は技術的に必要ありません。ただし、バッファリングされたデータがある可能性がある場合は、execエラーの処理方法に注意する必要があります。特に、stdio関数(write
は大丈夫です)を使用してエラーをstdoutまたはstderrに出力することを避け、exit
を呼び出すか(バッファーされたものをフラッシュする)代わりに__exit
_(または__Exit
_)を呼び出します出力)。または、フォークする前にフラッシュして問題を完全に回避します。