為什麼 cat x >> x 循環?
以下 bash 命令進入無限循環:
$ echo hi > x $ cat x >> x
我可以猜測它在開始寫入標準輸出後會
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
這兩種行為如何一致?什麼機制解釋了為什麼
cat
上面的程式碼沒有循環?
在我擁有的較舊的 RHEL 系統上,不會
/bin/cat
循環for . 給出錯誤消息“cat:x:輸入文件是輸出文件”。我可以通過這樣做來愚弄:。當我嘗試上面的程式碼時,我得到了你描述的“循環”。我還寫了一個基於系統呼叫的“貓”:cat x >> x``cat``/bin/cat``cat < x >> x
#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”不同)是核心中發生的事情。
我認為正在發生的事情是文件描述符 3(的結果
open(av[1])
)在文件中的偏移量為 0。歸檔描述符 1(stdout)的偏移量為 3,因為“>>”導致呼叫 shelllseek()
在在將文件描述符交給cat
子程序之前。執行
read()
任何類型的 a,無論是進入 stdio 緩衝區還是普通文件,都會char buf[]
提高文件描述符 3 的位置。執行 awrite()
會提高文件描述符 1 的位置。這兩個偏移量是不同的數字。由於“>>”,文件描述符 1 的偏移量總是大於或等於文件描述符 3 的偏移量。所以任何“類似貓”的程序都會循環,除非它做一些內部緩衝。有可能,甚至可能,一個包含其自己的緩衝區的 stdio 實現FILE *
(它是符號的類型stdout
和您的程式碼中的類型)。實際上可能會執行系統呼叫來填充內部緩衝區 fo 。這可能會也可能不會改變. 呼籲_f``fread()``read()``f``stdout``fwrite()``stdout
可能會也可能不會改變f
. 所以基於標準輸入輸出的“貓”可能不會循環。或者它可能。如果不閱讀大量醜陋、醜陋的 libc 程式碼,很難說。我
strace
在 RHEL 上做了一個cat
- 它只是做了一系列的read()
系統write()
呼叫。但是 acat
不必以這種方式工作。有可能mmap()
是輸入文件,然後做write(1, mapped_address, input_file_size)
. 核心將完成所有工作。或者您可以sendfile()
在 Linux 系統上的輸入和輸出文件描述符之間進行系統呼叫。有傳言說舊的 SunOS 4.x 系統會使用記憶體映射技巧,但我不知道是否有人做過基於 sendfile 的貓。在任何一種情況下,都不會發生“循環”,因為兩者都write()
需要sendfile()
傳輸長度參數。