使用内存映射复制文件

4

我希望在运行于BSD操作系统的进程中实现一种有效的C文件复制技术。目前,该功能是使用读写技术实现的。我正在尝试通过使用内存映射文件复制技术来优化它。

基本上,我将分叉一个进程,mmap源文件和目标文件,并从源到目标执行指定字节的memcpy()。memcpy()返回后,该进程退出。这里需要使用msync()吗?因为当我实际调用带有MS_SYNC标志的msync()函数时,该函数需要很长时间才能返回。使用MS_ASYNC标志时也会出现相同的行为吗?

i)总结一下,避免使用msync()是否安全?

ii)在BSD中是否有更好的文件复制方式?因为BSD似乎不支持sendfile()或splice()。有其他等效方法吗?

iii)是否有简单的方法实现自己的零拷贝技术以满足此要求?

我的代码

/* mmcopy.c

   Copy the contents of one file to another file, using memory mappings.

   Usage mmcopy source-file dest-file
*/
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    char *src, *dst;
    int fdSrc, fdDst;
    struct stat sb;

    if (argc != 3)
        usageErr("%s source-file dest-file\n", argv[0]);

    fdSrc = open(argv[1], O_RDONLY);
    if (fdSrc == -1)
        errExit("open");

    /* Use fstat() to obtain size of file: we use this to specify the
       size of the two mappings */

    if (fstat(fdSrc, &sb) == -1)
        errExit("fstat");

    /* Handle zero-length file specially, since specifying a size of
       zero to mmap() will fail with the error EINVAL */

    if (sb.st_size == 0)
        exit(EXIT_SUCCESS);

    src = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fdSrc, 0);
    if (src == MAP_FAILED)
        errExit("mmap");

    fdDst = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
    if (fdDst == -1)
        errExit("open");

    if (ftruncate(fdDst, sb.st_size) == -1)
        errExit("ftruncate");

    dst = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdDst, 0);
    if (dst == MAP_FAILED)
        errExit("mmap");

    memcpy(dst, src, sb.st_size);       /* Copy bytes between mappings */
    if (msync(dst, sb.st_size, MS_SYNC) == -1)
        errExit("msync");
    enter code here
    exit(EXIT_SUCCESS);
}

如果您不执行 msync(2),则需要注意。在您执行 munmap(2) 文件之前,无法保证您的更改已经被刷新回文件系统。 - Witiko
谢谢,但是msync()似乎需要相当长的时间,有没有更有效的方法? - Bose
对于BSD,我不太熟悉,无法提供权威的答案,很抱歉。 - Witiko
1
浏览一下 /bin/cp 的 FreeBSD 源代码(https://svnweb.freebsd.org/base/head/bin/cp/)可以发现,他们仅对源文件进行 mmap(2) 操作,而且只有当源文件大小不超过 8M 时才这样做,然后使用 write(2) 发出目标数据。你确定你能做得更好吗?你有进行过测量吗?有时候,一个经过充分验证的系统实用程序的执行可以很好地结合可移植性和性能。 - sjnarv
我的方法是使用mmap()同时映射两个文件,并进行memcpy()操作,随后再调用msync()函数。但我有一个疑问,依赖于msync()函数能够获得确定性的结果是否安全? - Bose
显示剩余2条评论
1个回答

3
简短回答:不需要使用msync()
当您没有指定msync()时,操作系统会在进程终止后在后台刷新内存映射的页面。这在任何符合POSIX标准的操作系统上都是可靠的。
回答次要问题:
通常,在任何符合POSIX标准的操作系统(如BSD)上复制文件的方法是使用open() / read() / write()和某些大小的缓冲区(例如16kb、32kb或64kb)。从src读取数据到缓冲区,将数据从缓冲区写入dest。重复此过程,直到read(src_fd)返回0字节(EOF)。
然而,根据您的目标,使用mmap()以这种方式复制文件可能是一个完全可行的解决方案,只要被复制的文件相对较小(相对于目标硬件和应用程序的预期内存限制)。 mmap复制操作将需要大约2倍于文件的总物理内存。因此,如果您要复制一个8MB的文件,则应用程序将使用16MB来执行复制。如果您希望处理的文件比那更大,那么复制就会变得非常昂贵。
那么使用mmap()是否具有其他优点?实际上并没有。
  1. 操作系统在刷新mmap页面方面通常比使用write()直接写入文件的数据要慢得多。这是因为操作系统会有意将其他事情优先于页面刷新,以使系统对前台任务/应用程序保持“响应”。
  2. 在mmap页面被刷新到磁盘的时间(在后台)期间,突然断电的机会会导致数据丢失。当然,使用write()也可能发生这种情况,但如果write()完成得更快,则意外中断的可能性较小。
  3. 调用msync()时观察到的长时间延迟大约是操作系统将复制的文件刷新到磁盘所需的时间。当您不调用msync()时,它会在后台执行(因此需要更长的时间)。

感谢提供信息,jstine。 - Bose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接