像 cat 一样的 C 实现,具有鲁棒性和效率
Posted
技术标签:
【中文标题】像 cat 一样的 C 实现,具有鲁棒性和效率【英文标题】:C implement like cat with robustness and efficiency 【发布时间】:2021-06-22 22:42:17 【问题描述】:我想学习实现 cat 之类的功能,它只是从文件中获取输入并打印到标准输出。
但我不确定write()
的行是否在所有情况下都可靠,因为它可能写得少于n
。但我无法创建一个测试用例来实现这种情况。如何制作一个测试用例,使其可以写入少于 n 个字符?另外,如何相应地修改代码以使程序健壮(对于这种情况,也适用于我没有描述的其他情况)?
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[])
const char *pathname = argv[1];
int fd;
if((fd = open(pathname, O_RDONLY)) == -1)
perror("open");
return 1;
#define BUF_SIZE 1024
char buf[BUF_SIZE];
ssize_t n;
while((n = read(fd, &buf, BUF_SIZE)) > 0)
if(write(STDOUT_FILENO, &buf, n) == -1)
perror("write");
return 1;
if(n == -1)
perror("read");
return 1;
if(close(fd) == -1)
perror("close");
return 1;
return 0;
编辑:我根据 Armali 提到的管道阻塞测试用例修复了前面代码中的 write()
错误。谁能检查是否还有其他错误?
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[])
const char *pathname = argv[1];
int fd;
if((fd = open(pathname, O_RDONLY)) == -1)
perror("open");
return 1;
#define BUF_SIZE 2*65536
char buf[BUF_SIZE];
ssize_t r_n;
while((r_n = read(fd, &buf, BUF_SIZE)) > 0)
ssize_t w_n;
int i = 0;
while((w_n = write(STDOUT_FILENO, buf+i, r_n)) < r_n)
if(w_n == -1)
perror("write");
return 1;
r_n -= w_n;
i += w_n;
if(r_n == -1)
perror("read");
return 1;
if(close(fd) == -1)
perror("close");
return 1;
return 0;
【问题讨论】:
我没有看到您处理读取或写入的任何问题。 (实际上,您在验证每个步骤方面做得很好)为什么要使用系统调用而不是stdio.h
I/O?
write
有documented edge cases,其中可以写入的字节数少于n
。在这些情况下,您的代码确实会失败。我能想象的可靠测试的唯一方法是模拟写入(可能通过宏)。
对于小于 2^31 字节数据的简单 cat
程序,您尝试模拟的边缘情况并不常见。部分写入条件通常与网络写入相关联。在那里,您将循环直到写入n
字节,以跟踪每次调用写入的数字。另一种情况是磁盘已满错误(不会发生写入stdout
除非整个文件系统被其他东西填充(然后它将取决于实现)您对read
的使用已经限制为@987654334 @ (1024
字节)
除此之外:将&
放在两个地方:while((n = read(fd, &buf, BUF_SIZE)) > 0) if(write(STDOUT_FILENO, &buf, n) == -1)
。 buf
就足够了。
if(write(STDOUT_FILENO, &buf, n) == -1)
应该是if(write(STDOUT_FILENO, &buf, n) != n)
。
【参考方案1】:
如何制作一个测试用例,使其可以写入少于 n 个字符?
根据系统,这并不难。 man 2 write
告诉:
… partial writes can occur for various reasons; for example, because there was
insufficient space on the disk device to write all of the requested bytes, or because
a blocked write() to a socket, pipe, or similar was interrupted by a signal …
让我们关注管道写入,如果管道缓冲区已满,则会阻塞。此示例针对 Linux;其他系统上的数字可能会有所不同。 man 7 pipe
讲述管道容量:
Since Linux 2.6.11, the pipe capacity is 16 pages (i.e., 65,536 bytes in a system with
a page size of 4096 bytes).
为了在测试用例中使用它,我们必须将您的BUF_SIZE
增加到更大的值,例如1024*128
。然后我们就可以进行实验了:
~$ dd count=256 </dev/zero >zeros
256+0 records in
256+0 records out
131072 bytes (131 kB, 128 KiB) copied, 0.00357324 s, 36.7 MB/s
~$ strace -ewrite ./a.out zeros|(sleep 9; dd)
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 131072^Z
[1]+ Stopped strace -ewrite ./a.out zeros | ( sleep 9; dd )
~$ fg
strace -ewrite ./a.out zeros | ( sleep 9; dd )
) = 65536
--- SIGCONT si_signo=SIGCONT, si_code=SI_USER, si_pid=482, si_uid=2001 ---
+++ exited with 0 +++
128+0 records in
128+0 records out
65536 bytes (66 kB, 64 KiB) copied, 0.00546081 s, 12.0 MB/s
首先我们准备一个 128 KiB 的文件。
然后我们用strace -ewrite
运行程序,看看写操作;我们将其输出通过管道传输到(sleep 9; dd)
以延迟读取。在此延迟期间,我们通过按 Ctrl-Z 发出 STOP 信号。
最后,我们用fg
继续这个程序。现在我们看到,使用计数 131072
调用的写入操作仅返回写入的 65536
字节。
另外,如何相应地修改代码以使程序健壮(对于这种情况,也适用于我没有描述的其他情况)?
对于这种情况和类似情况,您可以按照 David C. Rankin 所写:
您将在此处循环直到写入
n
字节,跟踪每次调用写入的数字。
除了检查所有调用的库函数的返回值之外,我不能说如何使程序对未描述的情况保持健壮,而您不能确定它们是否成功。
【讨论】:
如果write()
返回0,我能确定write()
已经完成了所有输入而不是失败吗?
不,如果 write(fd, buf, count)
已经完成了所有输入的写入,它会返回传递的 count,因此只有通过了才会返回零count 为零,这是不可取的,因为这种情况的规则有些复杂且部分未指定。
那么如果write()
的count参数不为零,但write()
返回0,那么我可以确定写入成功完成了吗?
我不确定您所说的完成是什么意思。如果返回 0,则 write
的调用完成且没有错误,但是请求的 count 个字节的写入当然还没有完成,必须重试。您的固定程序可以做到这一点。以上是关于像 cat 一样的 C 实现,具有鲁棒性和效率的主要内容,如果未能解决你的问题,请参考以下文章