Linuxでmmap()
を介してメモリマップドI/Oを使用して、ファイルの内容を別のファイルにコピーしてみます。 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に感謝します。
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つのバグがあります。
元のファイル:O_RDONLYオープン、MAP_PRIVATE mmap
宛先ファイル:O_WRONLYオープン、MAP_SHARED mmap
MAP_SHAREDを使用するには、O_RDWRフラグを使用して開く必要があります。
実際にMAP_FILEを実行する必要はありません| MAP_SHARED?
これは私にとってはうまくいきます。宛先O_RDWRを開かなければならなかったことに注意してください。一度に1バイトまたはWordを更新しているため、カーネルがファイルからメモリにページ全体をマップ(読み取り)しようとしていると思われますが、ページ全体が変更されない可能性があります。
他のいくつかのポイント:
終了するだけの場合は、エラー時にコンテンツを閉じてマップを解除する必要はありません。
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;
}