読み取りバッファサイズが2048の場合は機能しますが、バッファサイズが48または他の数値の場合、書き込み呼び出しはブロックされます。なぜですか?

読み取りバッファサイズが2048の場合は機能しますが、バッファサイズが48または他の数値の場合、書き込み呼び出しはブロックされます。なぜですか?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char* argv[]){
    if(argc < 2){ 
        puts("err argv");
        return -1; 
    }   

    int r_fd = open(argv[1], O_RDWR);
    int w_fd = open(argv[1], O_RDWR);

    fd_set r_set;
    fd_set w_set;
    char w_buf[4096];
    char r_buf[2048];
    int read_count = 0,write_count = 0;  
    while(1){
        FD_ZERO(&r_set);
        FD_ZERO(&w_set);
        FD_SET(r_fd, &r_set);
        FD_SET(w_fd, &w_set);
        int ret = select(w_fd + 1, &r_set, &w_set, NULL, NULL);
        if(FD_ISSET(r_fd, &r_set)){
            read(r_fd, r_buf, sizeof(r_buf));    
            printf("read count:%d\n", read_count++);    
        }   
        if(FD_ISSET(w_fd, &w_set)){
            write(w_fd, w_buf, sizeof(w_buf));
            printf("write count:%d\n", write_count++);  
        }   
        //sleep(1);
    }   

    return 0;
}

実行コード: mkfifo 1.fifo && gcc main.c -o main && ./main 1.fifo

答え1

        if(FD_ISSET(w_fd, &w_set)){
            write(w_fd, w_buf, sizeof(w_buf));
            printf("write count:%d\n", write_count++);  
        }   

selectfdが書き込み可能であると言われるならば、これは次のように書くことができるという意味です。最小1バイト。 FIFOバッファの管理方法によっては、しきい値が1より高くなる可能性がありますが、これはブロックなしで4096バイトを書き込むことができるという意味ではありません。

このコードは静的に間違っています。合理的に頑丈にするには、両方のfdを非ブロックモードに設定する必要があります(そして適切にコードを処理EAGAIN/返すために準備する必要があります)。EWOULDBLOCK

これ理由w_fd読み込みバッファサイズが原因で公開される問題のいくつかは、FIFOが十分に空ですが(書き込み可能に見えるように)書き込みを完了するのに十分すぎて(したがってブロック)状況に到達できるかどうかに関するものです。

ブロックされたプロセスにデバッガを接続するか、straceで実行してそれを確認または偽造することができます。これらは使い方を学ぶ必要がある完全に一般的なツールです。

答え2

問題は、単一の選択後にr_setとw_setをチェックしたときに発生するようです。読み取りまたは書き込みの競合状態によって結果が無効になる可能性があります。

これは、追加のデバッグといくつかの結果を含む強力なバージョンです。

書き込みの前に利用可能な読み取りを実行することが重要です。書き込み優先順位を付与すると、読み取り前にfifo(最大64 KB)が入力されます。これは、元のコードの競合状態の一部である可能性があります。

//  xSelect.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char* argv[]){
    if(argc < 2){
        puts("err argv");
        return -1;
    }
    ssize_t r_res, w_res;
    int r_fd, w_fd;
    r_fd = open(argv[1], O_RDWR);
    w_fd = open(argv[1], O_RDWR); 
    fprintf (stderr, "r_fd %d, w_fd %d\n", r_fd, w_fd);

    fd_set r_set;
    fd_set w_set;
    char w_buf[4096];
    char r_buf[1235];

    for (int j = 0, w = 0; j < 20; j++) {
        FD_ZERO(&r_set);
        FD_ZERO(&w_set);
        FD_SET(r_fd, &r_set);
        FD_SET(w_fd, &w_set);
        int ret = select(w_fd + 1, &r_set, &w_set, NULL, NULL);
        if(FD_ISSET(r_fd, &r_set)){
            r_res = read(r_fd, r_buf, sizeof(r_buf));
            fprintf (stderr, "Read %zd\n", r_res);
        } else if(w++ < 3 && FD_ISSET(w_fd, &w_set)){
            w_res = write(w_fd, w_buf, sizeof(w_buf));
            fprintf (stderr, "Write %zd\n", w_res);
        } else {
            fprintf (stderr, "Tick\n");
        }
    }
    return 0;
}
$ make xSelect && echo Run && ./xSelect xSelect.fifo
cc xSelect.c -o xSelect
Run
r_fd 3, w_fd 4
Write 4096
Read 1235
Read 1235
Read 1235
Read 391
Write 4096
Read 1235
Read 1235
Read 1235
Read 391
Write 4096
Read 1235
Read 1235
Read 1235
Read 391
Tick
Tick
Tick
Tick
Tick
$ 

編集:私はこれを考えすぎているかもしれません。私はr_setとw_setが単一の読み取りおよび/または書き込みに直接影響を受けないと思います。ただし、これらの操作によって削除されます。

元のコードでは、w_fdFIFOが完全にいっぱいになるまで常に書き込みが可能です。したがって、同じ数の長い書き込みと短い読み取りを実行すると、作成者は最終的に中断されます。短い読み取りが2048バイトの場合、fifoを埋めるには約32サイクルが必要です。短い読み取りが48バイトの場合、fifoを埋めるには17サイクルが必要です。

明らかに、長期的に平均書き込み速度と読み取り速度は同じでなければなりません。通常、カーネルはビルダー(他のプロセスにあります)を予約しないことでこれを行いますが、コードは読み取り1回、書き込み1回を強制します。だからw_set.w_fd結局不安定になります。

1つの問題は、select()が書き込むデータの量がわからないことです。 fifoに1バイトのスペースがある場合、w_fdは書き込み可能です。書き込みが切り捨てられたのか、ブロックされているのか、失敗したのか、同じfifoを使う2つのfdの間に違いがあるのか​​はわかりません。答えはOSによって異なり、FIFOやソケットを使用しているかどうか、または他の要因によって異なりますので、テストする価値はありません。部分書き込みを処理し、EAGAINエラーを返すようにコードを記述する必要があります。そしてブロックする

あなたのコードはfifoを完全に埋めるのに苦労しますが、私のコードはすべての書き込み後にfifoを不必要に完全に空にするのが難しいです。適切なデモでは、可能性を調べるために動作を調整(またはランダム化)する必要があります。

関連情報