像 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 字节) 除此之外:将&amp; 放在两个地方:while((n = read(fd, &amp;buf, BUF_SIZE)) &gt; 0) if(write(STDOUT_FILENO, &amp;buf, n) == -1) buf 就足够了。 if(write(STDOUT_FILENO, &amp;buf, n) == -1) 应该是if(write(STDOUT_FILENO, &amp;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 实现,具有鲁棒性和效率的主要内容,如果未能解决你的问题,请参考以下文章

对云台IMU鲁棒性和硬件时间同步的理解

剑指offer3.4-代码的鲁棒性

RANSAC回归的鲁棒性

二叉树遍历操作的实现以及鲁棒性的完善

关于鲁棒性的思考

PCL 欧氏聚类分割