我想通过mmap()
在Linux中使用内存映射I / O来尝试将文件内容复制到另一个文件。目的是自己检查这是否比使用fread()
和fwrite()
更好,以及它如何处理大文件(例如,像几个GiBs,因为文件读取完整我想知道如果我需要有足够的内存量。)
这是我现在正在使用的代码:
// 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发现它。
答案 0 :(得分:14)
来自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个错误。
答案 1 :(得分:1)
这对我有用。请注意,我必须打开目标O_RDWR。我怀疑内核试图将整个页面从文件映射到内存(读取它),因为你一次更新一个字节或一个字,这可能不会改变整个页面。
其他几点:
如果您要退出,则无需关闭和取消映射错误。
使用memcpy并且不要编写自己的字节复制循环。一般来说Memcpy会更好地优化。 (虽然它并不总是最好的。)
您可能想要阅读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;
}
答案 2 :(得分:1)
原始文件:O_RDONLY打开,MAP_PRIVATE mmap
目标文件:O_WRONLY打开,MAP_SHARED mmap
您需要打开O_RDWR标志才能使用MAP_SHARED。
你真的不需要做MAP_FILE | MAP_SHARED?