POSIX 環境では、ファイルにアクセスする方法が少なくとも 2 つあります。標準的なシステム コールopen()
、read()
、write()
、およびその仲間がありますがmmap()
、ファイルを仮想メモリにマップするために使用するオプションもあります。
どちらか一方を使用するのが望ましいのはいつですか? 2 つのインターフェイスを含める価値がある個々の利点は何ですか?
mmap
複数のプロセスが同じファイルから読み取り専用の方法でデータにアクセスしている場合に最適です。これは、私が書いた種類のサーバー システムでは一般的です。 mmap
これらすべてのプロセスが同じ物理メモリ ページを共有できるため、多くのメモリを節約できます。
mmap
また、オペレーティング システムがページング操作を最適化できるようにします。たとえば、2 つのプログラムを考えてみましょう。で作成したバッファにファイルA
を読み込むプログラムと、1MBのファイルをメモリに読み込むプログラムB。オペレーティング システムがのメモリの一部をスワップ アウトする必要がある場合、メモリを再利用する前に、スワップするバッファの内容を書き込む必要があります。の場合、変更されていないページは、元の既存のファイルから復元する方法を OS が認識しているため、すぐに再利用できます。(OS は、最初に書き込み可能なページを読み取り専用としてマークし、コピー オン ライト戦略 と同様にseg faultをキャッチすることで、どのページが変更されていないかを検出できます)。1MB
malloc
mmaps
A
B
mmap
mmap
mmap
mmap
プロセス間通信にも役立ちます。通信する必要がmmap
あるプロセスでファイルを読み取り/書き込みとして使用し、mmap'd
領域で同期プリミティブを使用できます (これがMAP_HASSEMAPHORE
フラグの目的です)。
32 ビット マシンで非常に大きなファイルを処理する必要があるmmap
場合は、扱いにくい場合があります。これはmmap
、プロセスのアドレス空間で、マップされるファイルの範囲全体に収まる十分な大きさのアドレスの連続ブロックを見つける必要があるためです。これは、アドレス空間が断片化された場合に問題になる可能性があります。この場合、2 GB のアドレス空間が空いている可能性がありますが、その個々の範囲が 1 GB のファイル マッピングに適合しません。この場合、ファイルを収まるようにするよりも小さなチャンクでファイルをマップする必要がある場合があります。
mmap
読み取り/書き込みの代替としてのもう 1 つの厄介な問題は、ページ サイズのオフセットでマッピングを開始する必要があることです。オフセットでデータを取得したいだけの場合は、X
と互換性があるようにそのオフセットを修正する必要がありますmmap
。
最後に、一部の種類のファイルを操作する には、読み取り/書き込みが唯一の方法です。pipesやttysmmap
などには使用できません。
mmap() が利点ではないことがわかった領域の 1 つは、小さなファイル (16K 未満) を読み取る場合でした。ファイル全体を読み取るためのページ フォールトのオーバーヘッドは、単一の read() システム コールを実行する場合に比べて非常に高かった。これは、カーネルがタイム スライス内で完全に読み取りを満足できる場合があるためです。つまり、コードが切り替わることはありません。ページ フォールトが発生すると、別のプログラムがスケジュールされる可能性が高くなり、ファイル操作の待ち時間が長くなります。
mmap
大きなファイルにランダムアクセスできる場合に利点があります。もう1つの利点は、バッファリングに煩わされることなく、メモリ操作(memcpy、ポインタ演算)でアクセスできることです。バッファよりも大きな構造を持つバッファを使用すると、通常のI/Oが非常に困難になる場合があります。正しく処理するのが難しいことが多いコードを処理するには、mmapの方が一般的に簡単です。とはいえ、を使用する場合は特定のトラップがありmmap
ます。すでに述べたように、mmap
セットアップにはかなりのコストがかかるため、特定のサイズ(マシンごとに異なります)に対してのみ使用する価値があります。
madvise
ファイルへの純粋なシーケンシャルアクセスの場合、問題を軽減するための適切な呼び出しはありますが、それが常により良い解決策であるとは限りません。
アーキテクチャ(SPARC、itanium)の配置制限に注意する必要があります。読み取り/書き込みIOを使用すると、バッファは適切に配置されることが多く、キャストされたポインタを逆参照するときにトラップされません。
また、マップの外にアクセスしないように注意する必要があります。マップで文字列関数を使用し、ファイルの最後に\ 0が含まれていない場合、これは簡単に発生する可能性があります。最後のページが0で埋められているため、ファイルサイズがページサイズの倍数でない場合にほとんどの場合機能します(マップされた領域は常にページサイズの倍数のサイズになります)。
他の素晴らしい回答に加えて、Google の専門家 Robert Love によって書かれたLinux システム プログラミングからの引用:
の利点
mmap( )
経由でファイルを操作すると、標準コールやシステム コール
mmap( )
よりもいくつかの利点があります。その中には次のものがあります。read( )
write( )
メモリ マップト ファイルの読み取りと書き込みにより、
read( )
またはwrite( )
システム コールを使用するときに発生する余分なコピーが回避されます。ページ フォールトの可能性は別として、メモリ マップト ファイルの読み取りと書き込みでは、システム コールやコンテキスト スイッチのオーバーヘッドは発生しません。メモリにアクセスするのと同じくらい簡単です。
複数のプロセスが同じオブジェクトをメモリにマップすると、データはすべてのプロセス間で共有されます。読み取り専用および共有書き込み可能マッピングは、全体が共有されます。プライベートな書き込み可能マッピングには、まだ COW (コピーオンライト) されていないページが共有されています。
マッピングのシークには、単純なポインター操作が含まれます。
lseek( )
システムコールは必要ありません。これらの理由から、
mmap( )
は多くのアプリケーションにとって賢明な選択です。の短所
mmap( )
を使用する際の注意点がいくつかあります
mmap( )
。
メモリ マッピングのサイズは、常に整数のページ数です。したがって、バッキング ファイルのサイズと整数のページ数との差は、スラック スペースとして「浪費」されます。小さなファイルの場合、マッピングのかなりの割合が無駄になる可能性があります。たとえば、4 KB のページでは、7 バイトのマッピングで 4,089 バイトが無駄になります。
メモリ マッピングは、プロセスのアドレス空間に収まる必要があります。32 ビットのアドレス空間では、非常に多数のさまざまなサイズのマッピングによってアドレス空間が断片化され、大きな空き連続領域を見つけることが難しくなる可能性があります。もちろん、この問題は 64 ビットのアドレス空間ではあまり目立たなくなります。
カーネル内のメモリ マッピングと関連するデータ構造の作成と維持にはオーバーヘッドがあります。このオーバーヘッドは一般に、前のセクションで説明した二重コピーを排除することで回避できます。特に、サイズが大きく頻繁にアクセスされるファイルの場合はそうです。
これらの理由から、 の利点は
mmap( )
、マップされたファイルが大きい場合 (したがって、無駄なスペースがマップ全体のわずかな割合である場合)、またはマップされたファイルの合計サイズがページ サイズで割り切れる場合 (したがって、無駄なスペースはありません)。
メモリ マッピングには、従来の IO と比較して速度が大幅に向上する可能性があります。これにより、オペレーティング システムは、メモリ マップト ファイル内のページにアクセスしたときに、ソース ファイルからデータを読み取ることができます。これは、OS が検出した障害ページを作成することで機能し、OS は対応するデータをファイルから自動的に読み込みます。
これは、ページング メカニズムと同じように機能し、通常は、システム ページ境界とサイズ (通常は 4K) でデータを読み取ることにより、高速 I/O 用に最適化されます。これは、ほとんどのファイル システム キャッシュが最適化されるサイズです。