web-dev-qa-db-ja.com

Mmap()大きなファイル全体

次のコード(test.c)を使用して、バイナリファイル(〜8Gb)を「mmap」しようとしています。

_#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define handle_error(msg) \
  do { perror(msg); exit(EXIT_FAILURE); } while (0)

int main(int argc, char *argv[])
{
   const char *memblock;
   int fd;
   struct stat sb;

   fd = open(argv[1], O_RDONLY);
   fstat(fd, &sb);
   printf("Size: %lu\n", (uint64_t)sb.st_size);

   memblock = mmap(NULL, sb.st_size, PROT_WRITE, MAP_PRIVATE, fd, 0);
   if (memblock == MAP_FAILED) handle_error("mmap");

   for(uint64_t i = 0; i < 10; i++)
   {
     printf("[%lu]=%X ", i, memblock[i]);
   }
   printf("\n");
   return 0;
}
_

test.cは、_gcc -std=c99 test.c -o test_およびfileのテスト結果を使用してコンパイルされます:test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped

これは小さなファイルでは正常に機能しますが、大きなファイルをロードしようとするとセグメンテーションエラーが発生します。プログラムは実際に返します:

_Size: 8274324021 
mmap: Cannot allocate memory
_

Boost :: iostreams :: mapped_fileを使用してファイル全体をマッピングできましたが、Cおよびシステムコールを使用してそれを実行したいです。私のコードの何が問題になっていますか?

64
Emer

MAP_PRIVATEマッピングにはメモリ予約が必要です。これらのページに書き込むと、コピーオンライト割り当てが発生する可能性があるためです。これは、物理RAM +スワップよりも大きなものをマップできないことを意味します。代わりにMAP_SHAREDマッピングを使用してみてください。これは、マッピングへの書き込みがディスクに反映されることを意味します。そのため、カーネルは、ライトバックを行うことで常にメモリを解放できることを認識しているため、制限はありません。

また、PROT_WRITEを使用してマッピングしていることにも注意してください。その後、メモリマッピングから読み取ります。また、O_RDONLYでファイルを開きました-これ自体が別の問題である可能性があります。 O_RDWRPROT_WRITEを使用する場合は、MAP_SHAREDを指定する必要があります。

PROT_WRITEのみに関しては、x86は書き込み専用マッピングをサポートしていないが、他のプラットフォームではセグメンテーション違反を引き起こす可能性があるため、これはx86で機能します。 PROT_READ|PROT_WRITEをリクエストします-または、読む必要がある場合はPROT_READをリクエストします。

私のシステム(676MB RAM、256MBスワップのVPS)で、問題を再現しました。 MAP_SHAREDに変更すると、EPERMエラーになります(O_RDONLYで開いたバッキングファイルへの書き込みが許可されていないため)。 PROT_READおよびMAP_SHAREDに変更すると、マッピングが成功します。

ファイルのバイトを変更する必要がある場合、1つのオプションは、書き込み先のファイルの範囲だけをプライベートにすることです。つまり、munmapMAP_PRIVATEを使用して、書き込み先の領域を再マッピングします。もちろん、全体のファイルに書き込む場合は、そのために8GBのメモリが必要です。

あるいは、1/proc/sys/vm/overcommit_memory に書き込むこともできます。これにより、マッピング要求が成功します。ただし、実際に8GBのCOWメモリをすべて使用しようとすると、プログラム(または他のプログラム)がOOMキラーによって強制終了されることに注意してください。

63
bdonlan

そのマッピングを処理するための十分な仮想メモリがありません。

例として、私はここに8G RAMと〜8Gスワップ(したがって、利用可能な合計16Gの仮想メモリ)を備えたマシンを持っています。

〜8GのVirtualBoxスナップショットでコードを実行すると、正常に動作します。

$ ls -lh /media/vms/.../snap.vdi
-rw------- 1 me users 9.2G Aug  6 16:02 /media/vms/.../snap.vdi
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256 
[0]=3C [1]=3C [2]=3C [3]=20 [4]=4F [5]=72 [6]=61 [7]=63 [8]=6C [9]=65 

スワップをドロップすると、合計8Gのメモリが残ります。 (Do n'tこれをアクティブなサーバーで実行します。)結果は次のとおりです。

$ Sudo swapoff -a
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256 
mmap: Cannot allocate memory

そのため、そのマッピングを保持するのに十分な仮想メモリがあることを確認してください(そのファイル内のいくつかのページに触れただけでも)。

4
Mat

Linux(および明らかに他のいくつかのUNIXシステム)には、 mmap(2)MAP_NORESERVEフラグがあり、明示的にスワップを有効にするために使用できますスペースのオーバーコミット。これは、システムで使用可能な空きメモリの量よりも大きいファイルをマップする場合に役立ちます。

これは、MAP_PRIVATEとともに使用する場合に特に便利で、そうしないとファイル全体のスワップ領域の予約をトリガーするため(またはシステムがENOMEMを返す場合、ワイドオーバーコミットは有効になっておらず、システムの空きメモリを超えています。

注意すべき問題は、このメモリの大部分に書き込みを行うと、レイジースワップスペースの予約により、アプリケーションがすべての空きRAMとシステム上のスワップを消費し、最終的にOOMキラーをトリガーする(Linux)か、アプリがSIGSEGVを受け取るようにします。

4
dcoles