cat x >> xが繰り返されるのはなぜですか?

cat x >> xが繰り返されるのはなぜですか?

次のbashコマンドは無限ループに入ります。

$ echo hi > x
$ cat x >> x

stdoutへの書き込みを開始した後は、cat読み続けることを推測できます。xしかし、混乱するように、私のcatの私のテストの実装は異なる動作を示しています。

// mycat.c
#include <stdio.h>

int main(int argc, char **argv) {
  FILE *f = fopen(argv[1], "rb");
  char buf[4096];
  int num_read;
  while ((num_read = fread(buf, 1, 4096, f))) {
    fwrite(buf, 1, num_read, stdout);
    fflush(stdout);
  }

  return 0;
}

私が実行した場合:

$ make mycat
$ echo hi > x
$ ./mycat x >> x

もちろんいいえリングの形。アクションとcat再度呼び出す前にリフレッシュを呼び出すという事実を考慮すると、このCコードはループから読み書きを続けたいと思います。stdoutfread

これら2つの行動はどのように一致しますか?catループが発生したが上記のコードが発生しない理由を説明するメカニズムは何ですか?

ベストアンサー1

私が持っている以前のRHELシステムでは/bin/cat実際にそうです。いいえループcat x >> xcat「cat:x:入力ファイルが出力ファイルです」というエラーメッセージが表示されます。/bin/cat次のようにしてこれをだますことができますcat < x >> x。上記のコードを試してみると、説明する「ループ」が表示されます。また、「cat」ベースのシステムコールも作成しました。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int
main(int ac, char **av)
{
        char buf[4906];
        int fd, cc;
        fd = open(av[1], O_RDONLY);
        while ((cc = read(fd, buf, sizeof(buf))) > 0)
                if (cc > 0) write(1, buf, cc);
        close(fd);
        return 0;
}

これも繰り返されます。ここで唯一のバッファリングは(stdioベースの「mycat」とは異なり)カーネルで起こることです。

open(av[1])私の考えでは、ファイル記述子3(の結果)がファイルのオフセット0にあるということです。ファイル記述子1(stdout)はオフセット3にあります。なぜなら">>"を使用すると、ファイル記述子でシェルが呼び出され、子プロセスlseek()に渡されるためです。cat

read()stdio バッファに入るか、通常の操作を実行するかにかかわらず、あらゆる種類のタスクを実行すると、char buf[]ファイル記述子 3 の位置が改善されます。操作を実行すると、write()ファイル記述子1の位置が上がります。これら2つのオフセットは異なる数値です。 「>>」のため、ファイル記述子1のオフセットは常にファイル記述子3のオフセット以上である。したがって、「猫のような」プログラムは、内部バッファリングを実行しない限り繰り返されます。 a(コードのFILE *シンボルタイプstdout)のstdio実装に独自のバッファが含まれている可能性もあります。内部バッファを埋めるために実際にシステムコールを実行できます。内部的には何も変わるかもしれませんし、変わらないかもしれません。呼び出しは内部的に何も変更しないかもしれません。したがって、stdioベースの「cat」は繰り返されない可能性があります。それともそうかもしれません。見苦しいlibcコードをあまり読まないと言うのは難しいです。ffread()read()fstdoutfwrite()stdoutf

私はstraceRHELで1つを作りましたcat。これは一連のシステムコールread()のみを行いますwrite()。しかし、必ずしもcatこのように作業する必要はありません。ファイルがmmap()入力された後に実行できますwrite(1, mapped_address, input_file_size)。カーネルはすべてを行います。あるいは、sendfile()Linuxシステムでは、入出力ファイル記述子間でシステムコールを実行できます。以前のSunOS 4.xシステムでは、メモリマッピングトリックを実行するという噂がありますが、sendfileベースの猫を使用してそのようなことをした人がいるかどうかはわかりません。どちらの場合も、長さパラメータを送信するwrite()必要があるため、「ループ」は発生しません。sendfile()

おすすめ記事