パイプが閉じるまで読む

パイプが閉じるまで読む

私は現在、オペレーティングシステムの紹介に挑戦していますが、面白くて同時に混乱しています。今パイプライン作業中です。私のコードは次のとおりです。

当初、私のコードは次のようになりました。

// 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つは次のとおりです。

  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つの非常に単純なプログラムを書くことをお勧めします。

  1. 一部のテキストを標準出力に書き込んで終了するメソッドです。 「速い茶色のキツネが怠惰な犬を飛び越えました。」のように単純なものかもしれません。 "Lorem ipsum dolor sat amet, consectetur adipiscing elit,..."、何度も繰り返される短い文字列(単一文字かもしれません) - 必要に応じて何でも可能です。printfwriteまたはfprintf(stdout, …)必要な他の機能を使用してください。

    プログラムをテストするには、シェルプロンプトで実行します。選択したテキストが表示され終了する必要があります(シェルプロンプトに戻ります)。

  2. 標準入力からテキストを読み、標準出力に書き込みます。getcgetsまたはread必要な他の機能を使用してください。ファイルの終わりに達すると終了します。ファイルの終わりを示す方法については、使用する機能のマニュアルページを確認してください。

    プログラムをテストするには、テキストファイル(名前jon_file.txt)を作成し、その中にテキストを配置します。次のようにすばやく実行したり、echo "Hello world" > jon_file.txtエディタを使用したりできます。次に、を入力するとprog2 < jon_file.txtファイルの内容が表示され、終了します(シェルプロンプトに戻ります)。

pipe、または華やかな名前で呼び出さないでください。またはdupと呼ぶことはありません。 (何が起こっているのかを確実に理解できるように、デバッグおよび/または監査コードを含める必要があります。)その後、実行すると予想される結果が得られます。opencloseprog1 | 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ヶ月後にこの回答を振り返ると、私が実際に質問全体をカバーしていないことがわかります。前半部は扱ったが、前半部は扱わなかった。だから上記の内容を続けて、

  1. 最初のプログラムを修正して、次のことを書いてください。たくさん少なくとも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ステータス情報と  100000stdout102400に書き込まれたバイト数を表示する必要があります。 (これはwc -c標準入力(パイプ)から読み込んだバイト数を報告するの出力になります。)

  2. sleepはじめに10〜20秒前に読み取りを開始するように2番目のプログラムを修正してください。

    これをテストするには、prog2 < jon_file.txtもう一度実行してください。明らかにに指定した時間の間一時停止し、ファイルのsleep()内容を表示して終了する必要があります(シェルプロンプトに戻ります)。

今実行してくださいprog1 | prog2 > /dev/null。しかし、そうする前に何が起こるのかを推測してみることもできます。

    ︙

    ︙

    ︙

私はそれがいくつかのドットを印刷すると予想しました。おそらく8、おそらく64または65、おそらく他の数字かもしれません。その後、一時停止し、残りのポイントを印刷し、... 読んでいなくてもすぐに開始できるから!です。まだ書いています。パイプは読み取りを開始する準備ができるまでデータを保持できますが、特定のポイントまでのみ可能です。パイプにはバッファリング制限があります。これは、8000(または8192)、64000(または65536)、またはその他の数字にすることができます。パイプがいっぱいになると、システムは強制的に待機します。読み取りが開始されると、パイプを空にしてパイプにデータを増やすためのスペースを提供するため、書き込みを再開できます。prog1prog2prog2prog1prog2prog1

最初に上記の動作が表示されない場合は、数字を200,000バイト、30秒などに増やしてください。

したがって、先生が計画の最初の草案を批判したとき、彼の言葉は正しいです。 (または彼の言葉が完全にフィットし、あなたが彼を誤って引用した可能性があります。)ご存知のように、このバージョンのプログラムは、プログラム(パイプビルダー)が起動(パイプリーダー)にruncmd(pcmd->left)なる前に完了するのを待ちます。runcmd(pcmd->right)しかし、左側のプログラムが100,000バイトを出力した場合はどうなりますか?パイプをいっぱいにし、さらに書き込むのを待ちます。しかし、「誰か」がパイプから読み込み、ストレージバッファを使い果たすまで、これ以上書き込むことはできません。ただし、メインプログラムはパイプビルダーが完了するまでパイプリーダーを起動しません。誰もが他の人が何かをするのを待っていますが、最初の人がその仕事をするまでそれをしません。 (「お金をあげれば宝石をあげる」/「いいえ。病気後でお金を与えるあなた宝石をください。 「)はい。結論:パイプがいっぱいでデータを読み取るプロセスがなく、パイプを介したデータの移動が停止した場合、両方のプロセスは無期限に停止します。

この状態は文化的に思わず次のように呼ばれます。キャッチ22。コンピュータサイエンスの正式な名前は次のとおりです。二重ロック、非公式に呼び出された致命的な抱擁

おすすめ記事