Shell

為什麼 cat x >> x 循環?

  • April 26, 2020

以下 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()傳輸長度參數。

引用自:https://unix.stackexchange.com/questions/154903