
次の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コードはループから読み書きを続けたいと思います。stdout
fread
これら2つの行動はどのように一致しますか?cat
ループが発生したが上記のコードが発生しない理由を説明するメカニズムは何ですか?
答え1
私が持っている以前のRHELシステムでは/bin/cat
実際にそうです。いいえループcat x >> x
。cat
「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コードをあまり読まないと言うのは難しいです。f
fread()
read()
f
stdout
fwrite()
stdout
f
私はstrace
RHELで1つを作りましたcat
。これは一連のシステムコールread()
のみを行いますwrite()
。しかし、必ずしもcat
このように作業する必要はありません。ファイルがmmap()
入力された後に実行できますwrite(1, mapped_address, input_file_size)
。カーネルはすべてを行います。あるいは、sendfile()
Linuxシステムでは、入出力ファイル記述子間でシステムコールを実行できます。以前のSunOS 4.xシステムでは、メモリマッピングトリックを実行するという噂がありますが、sendfileベースの猫を使用してそのようなことをした人がいるかどうかはわかりません。どちらの場合も、長さパラメータを送信するwrite()
必要があるため、「ループ」は発生しません。sendfile()
答え2
最新のcat実装(sunos-4.0 1988)は、mmap()を使用してファイル全体をマップし、そのスペースに対して1x write()を呼び出します。仮想メモリがフルファイルマッピングを許可する限り、これらの実装は繰り返されません。
他の実装では、これはファイルがI / Oバッファより大きいかどうかによって異なります。
答え3
書かれた通りヘビーヒートトラップ、同じパイプからファイルを読み取り、ファイルに書き込むことはできません。
パイプが実行する操作によっては、ファイルが破損する可能性があります(0バイトまたはオペレーティングシステムのパイプバッファサイズと同じバイト数)。使用可能なディスク容量がいっぱいになるまで増やすか、そのポイントに到達できます。お使いのオペレーティングシステムのファイルサイズ制限やクォータなど
解決策は、テキストエディタまたは一時変数を使用することです。
答え4
両者の間にはある種の競争条件がありますx
。いくつかの実装cat
(例:coreutils 8.23)では、次のことを禁止しています。
$ cat x >> x
cat: x: input file is output file
これが検出されない場合、動作は明らかに実装によって異なります(バッファサイズなど)。
clearerr(f);
ファイル終了インジケータが設定されている場合は、次のコードからエラーが返されるfflush
場合に備えて、コードの後ろに追加してみることができます。fread
編集する:この行動は結局オースティンパネルディスカッション、失敗を許可します。