以下 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
会出现循环而上面的代码却不会?
答案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”不同)是内核中发生的事情。
我认为发生的情况是文件描述符 3( 的结果open(av[1])
)在文件中的偏移量为 0。文件描述符 1(stdout)的偏移量为 3,因为“>>”导致调用 shelllseek()
在文件描述符,然后将其交给cat
子进程。
执行read()
任何类型的 a 操作,无论是进入 stdio 缓冲区还是普通操作,都会使char buf[]
文件描述符 3 的位置前进。执行 a 操作会使write()
文件描述符 1 的位置前进。这两个偏移量是不同的数字。由于“>>”,文件描述符 1 的偏移量始终大于或等于文件描述符 3 的偏移量。因此任何“类似猫”的程序都会循环,除非它进行一些内部缓冲。有可能,甚至很可能, a 的 stdio 实现(这是代码中的FILE *
符号类型stdout
)包含其自己的缓冲区。实际上可能会执行系统调用来填充内部缓冲区 fo 。这可能会也可能不会改变 内部的任何内容。调用可能会也可能不会改变 内部的任何内容。因此基于 stdio 的“cat”可能不会循环。或者可能是这样。如果不阅读大量丑陋的 libc 代码,就很难说。f
fread()
read()
f
stdout
fwrite()
stdout
f
我strace
在 RHEL 上做了一个cat
- 它只是执行一系列的系统read()
调用write()
。但 acat
不一定要这样工作。可以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
如果设置了文件结束指示符,下一个将返回错误。
编辑:该行为最终被奥斯汀小组讨论,允许失败。