在Linux中使用read(),write()和open()API来复制一个巨大的文件

我一直在学习系统编程在Linux中,我试图复制一个video使用read()和write()。 我面临的问题是,我不能将整个文件保存到一个缓冲区,因为它是一个巨大的文件。

我以为我可以循环它,因为我正在使用附加标志写入,但那么我将如何使用它读?

这是我搞砸的代码。 我将不胜感激任何帮助:

int main() { int movie_rdfd = open("Suits.mp4", O_RDONLY); //fd for read off_t file_length = (int)(fseek(movie_rdfd, 0, SEEK_END)); printf("This is fd for open: %d\n", movie_rdfd); //line to be deleted char* Save[fseek(movie_rdfd, 0, SEEK_END)]; int C1 = read(movie_rdfd, Save, ); printf("Result of Read (C1): %d\n", C1); //line to be deleted int movie_wrfd = open("Suits_Copy.mp4", O_WRONLY|O_CREAT, 0644); //fd for write printf("This is result of open: %d\n", movie_wrfd); //line to be deleted int C2 = write(movie_wrfd, Save, fseek(movie_rdfd, 0, SEEK_END)); printf("Result of Read (C2): %d\n", C2); //line to be deleted close(movie_rdfd); close(movie_wrfd); return 0; } 

当我尝试查找文件大小时也显示分段错误

在POSIX.1系统(包括Linux)中复制文件的适当逻辑是粗略的

 Open source file Open target file Repeat: Read a chunk of data from source Write that chunk to target Until no more data to read Close source file Close target file 

正确的错误处理增加了大量的代码,但我认为这是必要的,而不是随后添加的可选的事情,如果有时间的话。

(我对此非常严格,即使他们的程序没有正常工作,我也会失败的,即使他们的程序没有正常工作,原因是基本的理智:一个可能炸毁你手中的工具不是一个工具,它是一个工具炸弹,软件世界已经有足够的炸弹,我们不需要更多的“程序员”来创建这些炸弹,我们需要的是可靠的工具。

以下是一个具有适当错误检查的示例实现:

 #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #define DEFAULT_CHUNK 262144 /* 256k */ int copy_file(const char *target, const char *source, const size_t chunk) { const size_t size = (chunk > 0) ? chunk : DEFAULT_CHUNK; char *data, *ptr, *end; ssize_t bytes; int ifd, ofd, err; /* NULL and empty file names are invalid. */ if (!target || !*target || !source || !*source) return EINVAL; ifd = open(source, O_RDONLY); if (ifd == -1) return errno; /* Create output file; fail if it exists (O_EXCL): */ ofd = open(target, O_WRONLY | O_CREAT | O_EXCL, 0666); if (ofd == -1) { err = errno; close(ifd); return err; } /* Allocate temporary data buffer. */ data = malloc(size); if (!data) { close(ifd); close(ofd); /* Remove output file. */ unlink(target); return ENOMEM; } /* Copy loop. */ while (1) { /* Read a new chunk. */ bytes = read(ifd, data, size); if (bytes < 0) { if (bytes == -1) err = errno; else err = EIO; free(data); close(ifd); close(ofd); unlink(target); return err; } else if (bytes == 0) break; /* Write that same chunk. */ ptr = data; end = data + bytes; while (ptr < end) { bytes = write(ofd, ptr, (size_t)(end - ptr)); if (bytes <= 0) { if (bytes == -1) err = errno; else err = EIO; free(data); close(ifd); close(ofd); unlink(target); return err; } else ptr += bytes; } } free(data); err = 0; if (close(ifd)) err = EIO; if (close(ofd)) err = EIO; if (err) { unlink(target); return err; } return 0; } 

该函数采用目标文件名(待创建),源文件名(从中读取)以及可选的块大小。 如果您提供0,则使用默认的块大小。 在当前的Linux硬件上,256k块大小应该达到最大吞吐量; 较小的块大小可能会导致某些(大型和快速)系统上的较慢的复制操作。

块大小应该是2的幂,或者2的大幂的小数倍。 由于块大小是由调用者选择的,因此它是使用malloc() / free()动态分配的。 请注意,它在错误情况下显式释放。

由于目标文件总是被创建的 – 函数将失败,如果目标文件已经存在,则返回EEXIST如果发生错误,则将其移除(“unlinked”),以便在错误情况下不遗留部分文件。 (忘记在错误路径中释放动态分配的数据是一个常见的错误;这通常被称为“泄漏内存”)。

open()read()write()close()unlink()的确切用法可以在Linux手册页找到 。

write()返回写入的字节数,如果发生错误则返回-1。 (请注意,我明确地将0和小于-1的所有负值视为I / O错误,因为它们通常不会发生。)

read()返回读取的字节数,如果发生错误则返回-1,如果不再有数据则返回0。

read()write()可以返回一个短计数 ; 即少于要求。 (在Linux中,大多数本地文件系统的普通文件不会发生这种情况,但只有白痴依赖于上述函数才能使用这些文件。处理短计数并不复杂,正如您从上面的代码中看到的那样。 )

例如,如果您想添加进度表,例如使用回调函数

 void progress(const char *target, const char *source, const off_t completed, const off_t total); 

那么在循环之前添加一个fstat(ifd, &info)调用是fstat(ifd, &info)的(使用struct stat info; off_t copied;后者计算复制的字节数)。 该调用也可能失败或报告info.st_size == 0 ,如果源是例如命名管道而不是普通文件。 这意味着total参数可能为零,在这种情况下,进度表将只显示以字节为单位的进度( completed ),剩余量未知。

这里有一些批评,然后我该怎么做:

这很好:

 int movie_rdfd = open("Suits.mp4", O_RDONLY); //fd for read 

这是嗯,不是很好:

 off_t file_length = (int)(fseek(movie_rdfd, 0, SEEK_END)); 

fseek()是用于基于stdioFILE *指针,用fopen() ,而不是来自open int文件描述符。 要获得用open()的文件的大小,请使用fstat()

 struct stat sb; int rc = fstat( movie_rdfd, &sb ); 

现在你知道文件有多大 但是,如果它是一个非常大的文件,它不会适应内存,所以这是不好的:

 char* Save[fseek(movie_rdfd, 0, SEEK_END)]; 

这在多方面也很糟糕 – 它应该是char Save[] ,而不是char * 。 但是,无论哪种方式,对于一个非常大的文件,这是不行的 – 它太大,不能作为本地变量放在堆栈上。

而且,你不想一次读完所有的东西 – 无论如何,这可能不会起作用,因为你可能会读取部分内容。 按照读取标准 :

read()函数将尝试从与打开的文件描述符关联的文件中读取 nbyte字节…

返回值

成功完成后,这些函数将返回一个非负整数,表示实际读取的字节数。 …

请注意,它表示“将尝试读取”,并返回“实际读取的字节数”。 所以你必须用循环来处理部分读取。

下面是使用open()read()write()来复制文件的一个非常简单的方法(注意它确实应该有更多的错误检查 – 例如, write()结果应该被检查以确保它们匹配读取的字节数):

 #define BUFSIZE ( 32UL * 1024UL ) char buffer[ BUFSIZE ]; int in = open( nameOfInputFile, O_RDONLY ); int out = open( nameOfOutputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644 ); // break loop explicitly when read fails or hits EOF for ( ;; ) { ssize_t bytesRead = read( in, buffer, sizeof( buffer ) ); if ( bytesRead <= 0 ) { break; } write( out, buffer, bytesRead ); } 

请注意,你甚至不需要知道文件有多大。

有很多事情可以做得更快一些 – 通常他们是不值得的,因为上面的代码可能会在大多数系统的最大IO速率的90%左右运行。