
私は現在、オペレーティングシステムの紹介に挑戦していますが、面白くて同時に混乱しています。今パイプライン作業中です。私のコードは次のとおりです。
当初、私のコードは次のようになりました。
// Child process - write
if (fork() == 0) {
fprintf(stderr, "Child\r\n");
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
// Parent process - read
} else {
wait(0);
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
fprintf(stderr, "Parent\r\n");
runcmd(pcmd->right);
}
これについての私の考え方は、親プロセスが子プロセスが終了するのを待ってからパイプからデータを読み取るだけです。私はこのコードをディスカッションページのメンターに投稿し、彼はコードにいくつかの問題があると言いました。そのうちの1つは次のとおりです。
- 子プロセスがパイプをブロックするのに十分な長さの入力として実行されると、親プロセスは無期限に停止する可能性があります。
したがって、彼はwc
データが利用可能になるまでパイプで待ってから、パイプが閉じられるまで読み取りを開始するブロック読み取りコマンドを使用することが正しい実装であると述べました。
私はパイプにデータがある間にパイプからデータを「読み取る」方法を見つけましたが、それを解決する方法がわかりません。最後に、ブロックパイプで永遠に待機する可能性のある問題を解決するために、親と子を同時に並列に実行しましたが、これは読み取りプロセスが最初に終了し、すべてのデータを読み取ることができない可能性があります。完了前に作成されました。この問題をどのように解決しますか?
int p[2];
pipe(p);
// Child process - read
if (fork() == 0) {
fprintf(stderr, "Start child\r\n");
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
fprintf(stderr, "Child\r\n");
runcmd(pcmd->right);
// Parent process - write
} else {
fprintf(stderr, "Start parent\r\n");
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
fprintf(stderr, "Parent\r\n");
runcmd(pcmd->left);
}
編集:また、コマンドを試しましたが、read
バッファと読み込む予想サイズ(?)が必要なので、実際に使用する方法がわかりません。着信データのサイズがわからない場合は、それを取得する方法がわかりません。
答え1
パイプラインは簡単です。プールの深いところに飛び込むと気分が悪くなります。 (または、よりよく教えていないのは先生のせいかもしれません。)
パイプに慣れるには、2つの非常に単純なプログラムを書くことをお勧めします。
一部のテキストを標準出力に書き込んで終了するメソッドです。 「速い茶色のキツネが怠惰な犬を飛び越えました。」のように単純なものかもしれません。 "Lorem ipsum dolor sat amet, consectetur adipiscing elit,..."、何度も繰り返される短い文字列(単一文字かもしれません) - 必要に応じて何でも可能です。
printf
、write
またはfprintf(stdout, …)
必要な他の機能を使用してください。プログラムをテストするには、シェルプロンプトで実行します。選択したテキストが表示され終了する必要があります(シェルプロンプトに戻ります)。
標準入力からテキストを読み、標準出力に書き込みます。
getc
、gets
またはread
必要な他の機能を使用してください。ファイルの終わりに達すると終了します。ファイルの終わりを示す方法については、使用する機能のマニュアルページを確認してください。プログラムをテストするには、テキストファイル(名前
jon_file.txt
)を作成し、その中にテキストを配置します。次のようにすばやく実行したり、echo "Hello world" > jon_file.txt
エディタを使用したりできます。次に、を入力するとprog2 < jon_file.txt
ファイルの内容が表示され、終了します(シェルプロンプトに戻ります)。
pipe
、または華やかな名前で呼び出さないでください。またはdup
と呼ぶことはありません。 (何が起こっているのかを確実に理解できるように、デバッグおよび/または監査コードを含める必要があります。)その後、実行すると予想される結果が得られます。open
close
prog1 | prog2
それでは、sleep
プログラムに呼び出しを追加して「中断」してみてください。壊れたらどうしたのか教えてください。ほとんど不可能です。 1つのプログラム(または2つのプログラム)を座って待つよりも長く眠らない場合は、常に作成されたすべてのprog2
データが出力されますprog1
。
上記の例で明確ではない場合:親プロセスと子プロセス(または通常はパイプの両側のプロセス)が「同時に」実行されるようにすることが正しいことです。1Readerは パイプにデータがないため、「最初に終了」されません。今。上記の練習からわかるように、プログラムが現在データのないパイプからデータを読み取ろうとすると、システムコールはプログラムがデータがread
到着するのを待つように強制します。パイプにデータがないまでリーダーは終了しません。
もう来なくて、一度。2 (この時点でread
ファイルの終わりが返されます。)「データが提供されなくなりました」という条件は、作成者がパイプを閉じる(または終了する、開いているすべてのファイル記述子が呼び出されるexit
ため、同じ)と表示されます。 。close
この時点で、なぜシステムコールを気にするのかわかりませんread
。しかし、まだシステムコールを使用する方法がわからない場合は、教師が指示に従わないように資料を提示しているという疑いがあります。論理的な順序。 (コマンドread
ではなくシステムコールを意味すると仮定しますread
。)プログラムが理解される唯一の方法は、上記のプログラムのようないくつかのruncmd(pcmd->right)
方法で標準入力から読み取ることです。prog2
あなたのプログラムは、シェルが何をしているのか、つまりパイプを設定してプログラムを実行することをやっているようです。このレベルでは、プログラムが(私たちに示した範囲内で)I / O(読み取りまたは書き込み)を実行する理由はありません。
__________
1関連資料:パイプラインコマンドはどの順序で実行されますか?
2もちろんこれは過度の単純化です。すぐに学ぶことになりますが、まだそうしていない場合は、パイプにデータがないときにリーダーが終了するように設計できます。今- しかし、これはデフォルトの動作ではありません。あるいは、他の条件(q
パイプから読み取るなど)でリーダーが終了するように設計することもできます。あるいは、信号などによって死亡することもあります。
6ヶ月後にこの回答を振り返ると、私が実際に質問全体をカバーしていないことがわかります。前半部は扱ったが、前半部は扱わなかった。だから上記の内容を続けて、
最初のプログラムを修正して、次のことを書いてください。たくさん少なくとも100,000(10 5)または102 400(2 10 ×10 2)文字のデータが標準出力に送信されます。また、まだ行っていない場合は、いくつかの継続的なステータス情報をstderrに記録するように修正してください。たとえば、
.
1000(または1024)文字ごとに ""をstderrに送信し、!\n
完了したら ""をstderrに送信します。これをテストするには、
prog1 > /dev/null
上記のアドバイスに従った場合は100ポイント(.
)の後に!
改行文字が続きます。sleep()
で呼び出しや時間がかかるその他の機能を操作しない場合、prog1
この出力は非常に迅速に表示されます。それから
prog1 | wc -c
。上記のように、stderrステータス情報と100000
stdout102400
に書き込まれたバイト数を表示する必要があります。 (これはwc -c
標準入力(パイプ)から読み込んだバイト数を報告するの出力になります。)sleep
はじめに10〜20秒前に読み取りを開始するように2番目のプログラムを修正してください。これをテストするには、
prog2 < jon_file.txt
もう一度実行してください。明らかにに指定した時間の間一時停止し、ファイルのsleep()
内容を表示して終了する必要があります(シェルプロンプトに戻ります)。
今実行してくださいprog1 | prog2 > /dev/null
。しかし、そうする前に何が起こるのかを推測してみることもできます。
︙
︙
︙
私はそれがいくつかのドットを印刷すると予想しました。おそらく8、おそらく64または65、おそらく他の数字かもしれません。その後、一時停止し、残りのポイントを印刷し、... 読んでいなくてもすぐに開始できるから!
です。まだ書いています。パイプは読み取りを開始する準備ができるまでデータを保持できますが、特定のポイントまでのみ可能です。パイプにはバッファリング制限があります。これは、8000(または8192)、64000(または65536)、またはその他の数字にすることができます。パイプがいっぱいになると、システムは強制的に待機します。読み取りが開始されると、パイプを空にしてパイプにデータを増やすためのスペースを提供するため、書き込みを再開できます。prog1
prog2
prog2
prog1
prog2
prog1
最初に上記の動作が表示されない場合は、数字を200,000バイト、30秒などに増やしてください。
したがって、先生が計画の最初の草案を批判したとき、彼の言葉は正しいです。 (または彼の言葉が完全にフィットし、あなたが彼を誤って引用した可能性があります。)ご存知のように、このバージョンのプログラムは、プログラム(パイプビルダー)が起動(パイプリーダー)にruncmd(pcmd->left)
なる前に完了するのを待ちます。runcmd(pcmd->right)
しかし、左側のプログラムが100,000バイトを出力した場合はどうなりますか?パイプをいっぱいにし、さらに書き込むのを待ちます。しかし、「誰か」がパイプから読み込み、ストレージバッファを使い果たすまで、これ以上書き込むことはできません。ただし、メインプログラムはパイプビルダーが完了するまでパイプリーダーを起動しません。誰もが他の人が何かをするのを待っていますが、最初の人がその仕事をするまでそれをしません。 (「お金をあげれば宝石をあげる」/「いいえ。病気後でお金を与えるあなた宝石をください。 「)はい。結論:パイプがいっぱいでデータを読み取るプロセスがなく、パイプを介したデータの移動が停止した場合、両方のプロセスは無期限に停止します。
この状態は文化的に思わず次のように呼ばれます。キャッチ22。コンピュータサイエンスの正式な名前は次のとおりです。二重ロック、非公式に呼び出された致命的な抱擁。