web-dev-qa-db-ja.com

select()は、fdが「準備完了」になるとどのように警告されますか?

なぜこれを見つけるのに苦労しているのかわかりませんが、準備ができていると報告するためにファイル記述子で待機しているselect()を使用しているLinuxコードを見ています。 selectのmanページから:

_select() and pselect() allow a program to monitor multiple file descriptors,
waiting until one or more of the file descriptors become "ready" for some
class of I/O operation 
_

だから、それは素晴らしいです...私はいくつかの記述子でselectを呼び出し、それにタイムアウト値を与え、指示が行くのを待ち始めます。ファイル記述子(または記述子の所有者)は、select()ステートメントが戻るように「準備ができている」とどのように報告しますか?

22
Mike

準備ができたと報告しますby戻ります。

selectは、通常はプログラムの制御外にあるイベントを待機します。本質的に、selectを呼び出すことにより、プログラムは「...まで処理することはありません。プロセスを中断してください」と言います。

指定する条件は一連のイベントであり、いずれのイベントもユーザーを呼び起こします。

たとえば、何かをダウンロードしている場合、ループは新しいデータの到着、転送が停止した場合のタイムアウト、またはユーザーの割り込みを待つ必要があります。これはまさにselectが行うことです。

複数のダウンロードがある場合、いずれかの接続に到着したデータはプログラムのアクティビティをトリガーします(データをディスクに書き込む必要があります)。したがって、リスト内のselectへのすべてのダウンロード接続のリストを提供します。 「読み取り」を監視するファイル記述子の数。

同時にどこかにデータをアップロードする場合、selectを再度使用して、接続が現在データを受け入れているかどうかを確認します。反対側がダイヤルアップにある場合、データの認識はゆっくりであるため、ローカル送信バッファーは常にいっぱいになり、さらにデータを書き込もうとすると、バッファースペースが使用可能になるか、失敗します。送信するファイル記述子を「書き込み」記述子としてselectに渡すことにより、バッファスペースが送信可能になるとすぐに通知されます。

一般的な考え方は、プログラムがevent-drivenになるということです。つまり、順次操作を実行するのではなく、一般的なメッセージループからの外部イベントに反応します。カーネルに「これは私が何かをしたいイベントのセットです」と言うと、カーネルは発生したイベントのセットを提供します。 2つのイベントが同時に発生することはかなり一般的です。たとえば、TCP確認応答がデータパケットに含まれていたため、同じfdを読み取り可能(データが利用可能)と書き込み可能(確認済みデータが送信バッファから削除された)にすることができます) selectを再度呼び出す前に、すべてのイベントを処理する準備をする必要があります。

細かい点の1つは、selectが基本的にreadまたはwriteの呼び出しがブロックされないという約束を、呼び出し自体についての保証なしに提供することです。たとえば、1バイトのバッファスペースが使用可能な場合、10バイトを書き込むと、カーネルが戻ってきて「1バイトを書き込みました」と言うことができるため、この場合も同様に処理できるように準備する必要があります。典型的なアプローチは、「このfdに書き込まれるデータ」バッファーを使用することです。空でない限り、fdは書き込みセットに追加され、「書き込み可能」イベントはすべてを書き込もうとして処理されます。現在バッファにあるデータ。バッファーがその後空になった場合は、そうでなくても問題ありません。再度「書き込み可能」になるまで待ちます。

「例外」セットはめったに使用されません。他のデータは通過する必要があるが、データ転送がブロックされる可能性がある帯域外データを持つプロトコルに使用されます。プログラムが現在「読み取り可能な」ファイル記述子からデータを受け入れられない場合(たとえば、ダウンロード中、ディスクがいっぱいの場合)、イベントを処理できないため、「読み取り可能な」セットに記述子を含めないでください。 selectは、再度呼び出されるとすぐに戻ります。受信者が「例外」セットにfdを含め、送信者がIPスタックに「緊急」データを含むパケットを送信するように要求した場合、受信者は起動され、未処理のデータを破棄して送信者と再同期することを決定できます。 telnetプロトコルは、たとえばCtrl-Cの処理にこれを使用します。そのような機能を必要とするプロトコルを設計している場合を除き、これを簡単に除外しても問題はありません。

必須のコード例:

#include <sys/types.h>
#include <sys/select.h>

#include <unistd.h>

#include <stdbool.h>

static inline int max(int lhs, int rhs) {
    if(lhs > rhs)
        return lhs;
    else
        return rhs;
}

void copy(int from, int to) {
    char buffer[10];
    int readp = 0;
    int writep = 0;
    bool eof = false;
    for(;;) {
        fd_set readfds, writefds;
        FD_ZERO(&readfds);
        FD_ZERO(&writefds);

        int ravail, wavail;
        if(readp < writep) {
            ravail = writep - readp - 1;
            wavail = sizeof buffer - writep;
        }
        else {
            ravail = sizeof buffer - readp;
            wavail = readp - writep;
        }

        if(!eof && ravail)
            FD_SET(from, &readfds);
        if(wavail)
            FD_SET(to, &writefds);
        else if(eof)
            break;
        int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL);
        if(rc == -1)
            break;
        if(FD_ISSET(from, &readfds))
        {
            ssize_t nread = read(from, &buffer[readp], ravail);
            if(nread < 1)
                eof = true;
            readp = readp + nread;
        }
        if(FD_ISSET(to, &writefds))
        {
            ssize_t nwritten = write(to, &buffer[writep], wavail);
            if(nwritten < 1)
                break;
            writep = writep + nwritten;
        }
        if(readp == sizeof buffer && writep != 0)
            readp = 0;
        if(writep == sizeof buffer)
            writep = 0;
    }
}

使用可能なバッファースペースがあり、読み取り側にファイルの終わりまたはエラーがなかった場合は読み取りを試み、バッファーにデータがある場合は書き込みを試みます。ファイルの終わりに達し、バッファが空の場合、処理は完了です。

このコードは明らかに最適ではありません(サンプルコード)が、カーネルが読み取りと書き込みの両方で要求したよりも少ないことを実行できることを確認できるはずです。準備ができています」、ブロックするかどうかを確認せずに読み取りまたは書き込みを行うことはありません。

29
Simon Richter

同じmanページから:

終了時に、セットはその場所で変更され、どのファイル記述子が実際にステータスを変更したかを示します。

したがって、selectに渡されたセットでFD_ISSET()を使用して、どのFDが準備完了になったかを判断します。