14

我想尝试通过在 Linux 中使用内存映射 I/O 将文件的内容复制到另一个文件mmap()。目的是自己检查这是否比使用更好fread()fwrite()以及它将如何处理大文件(例如几个 GiB,因为文件是完整读取的,我想知道我是否需要为它提供这么多的内存)。

这是我现在正在使用的代码:

// Open original file descriptor:
int orig_fd = open(argv[1], O_RDONLY);
// Check if it was really opened:
if (orig_fd == -1) {
    fprintf(stderr, "ERROR: File %s couldn't be opened:\n", argv[1]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    exit(EX_NOINPUT);
}
// Idem for the destination file:
int dest_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
// Check if it was really opened:
if (dest_fd == -1) {
    fprintf(stderr, "ERROR: File %s couldn't be opened:\n", argv[2]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close original file descriptor too:
    close(orig_fd);
    exit(EX_CANTCREAT);
}

// Acquire file size:
struct stat info = {0};
if (fstat(orig_fd, &info)) {
    fprintf(stderr, "ERROR: Couldn't get info on %s:\n", argv[1]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors:
    close(orig_fd);
    close(dest_fd);
    exit(EX_IOERR);
}
// Set destination file size:
if (ftruncate(dest_fd, info.st_size)) {
    fprintf(stderr, "ERROR: Unable to set %s file size:\n", argv[2]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors:
    close(orig_fd);
    close(dest_fd);
    exit(EX_IOERR);
}

// Map original file and close its descriptor:
char *orig = mmap(NULL, info.st_size, PROT_READ, MAP_PRIVATE, orig_fd, 0);
if (orig == MAP_FAILED) {
    fprintf(stderr, "ERROR: Mapping of %s failed:\n", argv[1]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors:
    close(orig_fd);
    close(dest_fd);
    exit(EX_IOERR);
}
close(orig_fd);
// Map destination file and close its descriptor:
char *dest = mmap(NULL, info.st_size, PROT_WRITE, MAP_SHARED, dest_fd, 0);
if (dest == MAP_FAILED) {
    fprintf(stderr, "ERROR: Mapping of %s failed:\n", argv[2]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors and unmap first file:
    munmap(orig, info.st_size);
    close(dest_fd);
    exit(EX_IOERR);
}
close(dest_fd);

// Copy file contents:
int i = info.st_size;
char *read_ptr = orig, *write_ptr = dest;
while (--i) {
    *write_ptr++ = *read_ptr++;
}

// Unmap files:
munmap(orig, info.st_size);
munmap(dest, info.st_size);

我认为这可能是一种方法,但我在尝试映射目标文件时不断收到错误,具体是代码 13(权限被拒绝)。

我不知道它为什么会失败,我可以写入该文件,因为该文件已创建,并且我尝试复制的所有文件的大小仅为几个 KiB。

有人能发现问题吗?为什么我有权映射原始文件而不是目标文件?

注意:如果有人要使用循环来复制问题中发布的字节而不是memcpy例如,则循环条件应该是i--复制所有内容。感谢 jxh 发现了这一点。

4

3 回答 3

17

mmap()手册页:

EACCES
文件描述符是指非常规文件。或者请求了 MAP_PRIVATE,但 fd 未打开以供读取。或者请求了 MAP_SHARED 并设置了 PROT_WRITE,但 fd 未在读/写 (O_RDWR) 模式下打开。或者设置了 PROT_WRITE,但文件是仅附加的。

您正在使用O_WRONLY. 改为使用O_RDWR

此外,您应该使用memcpy复制内存而不是使用自己的循环:

memcpy(dest, orig, info.st_size);

您的循环有 1 个错误。

于 2013-06-19T23:45:02.567 回答
1

这对我有用。请注意,我必须打开目标 O_RDWR。我怀疑内核试图将整个页面从文件映射到内存(读取它),因为您一次更新一个字节或一个字,这可能不会改变整个页面。

其他几点:

  1. 如果您只是要退出,则不需要关闭和取消映射错误的东西。

  2. 使用 memcpy 并且不要编写自己的字节复制循环。Memcpy 通常会得到更好的优化。(虽然它并不总是绝对最好的。)

  3. 您可能想阅读 FreeBSD 的“cp”实用程序的源代码。看看这里,搜索一下mmap的使用。http://svnweb.freebsd.org/base/stable/9/bin/cp/utils.c?revision=225736&view=markup


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
        int s, d;
        struct stat st;
        void *sp, *dp;
        s = open(argv[1], O_RDONLY);
        if (s == -1) {
                perror("open source");
                exit(1);
        }
        d = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644);
        if (d == -1) {
                perror("open destintation");
                exit(1);
        }
        if (fstat(s, &st)) {
                perror("stat source");
                exit(1);
        }
        if (ftruncate(d, st.st_size)) {
                perror("truncate destination");
                exit(1);
        }
        sp = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, s, 0);
        if (sp == MAP_FAILED) {
                perror("map source");
                exit(1);
        }
        dp = mmap(NULL, st.st_size, PROT_WRITE | PROT_READ, MAP_SHARED, d, 0);
        if (dp == MAP_FAILED) {
                perror("map destintation");
                exit(1);
        }
        memcpy(dp, sp, st.st_size);
        return 0;
}
于 2013-06-19T23:49:44.300 回答
1

原始文件:O_RDONLY 打开,MAP_PRIVATE mmap

目标文件:O_WRONLY 打开,MAP_SHARED mmap

您需要使用 O_RDWR 标志打开才能使用 MAP_SHARED。

你真的不需要做 MAP_FILE | MAP_SHARED ?

于 2013-06-19T23:54:21.417 回答