GNU/Linux >> Linux の 問題 >  >> Linux

古いマッピングを解放せずにLinux mremap?

それは可能ですが、考慮が必要なアーキテクチャ固有のキャッシュの一貫性の問題があります。一部のアーキテクチャでは、一貫性を失わずに複数の仮想アドレスから同時に同じページにアクセスすることを許可していません。そのため、これをうまく処理できるアーキテクチャもあれば、そうでないアーキテクチャもあります。

追加するために編集:AMD64 Architecture Programmer's Manual vol. 2、システム プログラミング、セクション 7.8.7 メモリ タイプの変更、状態:

<ブロック引用>

物理ページには、異なる仮想マッピングを介して割り当てられた異なるキャッシュ可能性タイプを持たせるべきではありません。それらはすべてキャッシュ可能なタイプ (WB、WT、WP) であるか、すべてキャッシュ不可能なタイプ (UC、WC、CD) である必要があります。そうしないと、キャッシュの一貫性が失われ、データが古くなり、予期しない動作が発生する可能性があります。

したがって、AMD64 では mmap() まで安全なはずです。 同じ prot である限り、同じファイルまたは共有メモリ領域を再び そして flags 使用されています;これにより、カーネルは各マッピングに対して同じキャッシュ可能な型を使用するようになります。

最初のステップは、メモリ マップに常にファイル バッキングを使用することです。 mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0) を使用 マッピングがスワップを予​​約しないようにします。 (これを忘れると、多くのワークロードで実際の限界に達するよりもずっと早くスワップの限界に達してしまいます。) ファイル バッキングによって生じる余分なオーバーヘッドは、まったく無視できます。

追加するために編集:ユーザー strcmp は、現在のカーネルがアドレス空間のランダム化をアドレスに適用していないことを指摘しました。幸いなことに、これは簡単に修正できます。ランダムに生成されたアドレスを mmap() に渡すだけです。 NULL の代わりに . x86-64 では、ユーザー アドレス空間は 47 ビットであり、アドレスはページ アラインされている必要があります。たとえば、次のように使用できます。 Xorshift* を使用してアドレスを生成し、不要なビットをマスクします:& 0x00007FFFFE00000 たとえば、2097152 バイトでアラインされた 47 ビット アドレスが得られます。

バッキングはファイルに対するものであるため、ftruncate() を使用してバッキング ファイルを拡大した後、同じファイルへの 2 つ目のマッピングを作成できます。 .適切な猶予期間が経過した後 (マッピングを使用しているスレッドがなくなったことがわかっている場合 (アトミック カウンターを使用してそれを追跡している場合など))、元のマッピングのマッピングを解除します。

実際には、マッピングを拡大する必要がある場合は、まずバッキング ファイルを拡大してから mremap(mapping, oldsize, newsize, 0) を試します。 マッピングを移動せずにマッピングを拡張できるかどうかを確認します。インプレース再マッピングが失敗した場合のみ、新しいマッピングに切り替える必要があります。

追加するために編集:mremap() を使用することは間違いありません mmap() を使用する代わりに と MAP_FIXED mmap() のため、より大きなマッピングを作成するには 他のファイルまたは共有メモリ領域に属するものを含め、既存のマッピングを (アトミックに) アンマップします。 mremap() で 、拡大されたマッピングが既存のマッピングと重複する場合、エラーが発生します。 mmap() で と MAP_FIXED 、新しいマッピングが重複する既存のマッピングは無視されます (マッピング解除されます)。

残念ながら、カーネルが既存のマッピング間の衝突を検出するかどうか、またはプログラマーがそのような衝突を知っていると想定しているだけなのかどうかを確認していないことを認めなければなりません。結局のところ、プログラマーはすべてのマッピングのアドレスと長さを知っている必要があります。マッピングが別の既存のものと衝突するかどうかを知っています。追加するために編集:3.8 シリーズのカーネルはそうし、MAP_FAILED を返します errno==ENOMEM で 拡大されたマッピングが既存のマップと衝突する場合。すべての Linux カーネルが同じように動作することを期待していますが、x86_64 上の 3.8.0-30-generic でのテストを除いて、証拠はありません。

また、Linux では、POSIX 共有メモリは特別なファイルシステム (通常は /dev/shm にマウントされた tmpfs) を使用して実装されることに注意してください。 (または /run/shm /dev/shm で シンボリックリンクです)。 shm_open() ら。 al は C ライブラリによって実装されます。大規模な POSIX 共有メモリ機能を持つ代わりに、カスタム アプリケーションで使用するために特別にマウントされた tmpfs を個人的に使用します。他の理由がなければ、セキュリティ制御 (ユーザーとグループがそこで新しい「ファイル」を作成できる) は、管理がはるかに簡単で明確になります。

マッピングが匿名であり、匿名でなければならない場合でも、 mremap(mapping, oldsize, newsize, 0) を使用できます 試す サイズを変更します。失敗するかもしれません。

数十万のマッピングがあっても、64 ビットのアドレス空間は広大であり、失敗するケースはほとんどありません。したがって、失敗の場合も処理する必要がありますが、必ずしも高速である必要はありません。 . 変更するために編集:x86-64 では、アドレス空間は 47 ビットであり、マッピングはページ境界 (通常のページの場合は 12 ビット、2M のヒュージページの場合は 21 ビット、1G のヒュージページの場合は 30 ビット) から開始する必要があるため、マッピング用のアドレス空間で使用可能な 35、26、または 17 ビット。そのため、ランダムなアドレスが提案されたとしても、衝突はより頻繁に発生します。 (2M のマッピングでは、1024 のマップで時折衝突が発生しましたが、65536 のマップでは、衝突 (サイズ変更の失敗) の確率は約 2.3% でした。)

追加するために編集:ユーザー strcmp は、デフォルトで Linux mmap() というコメントで指摘しました 連続したアドレスを返します。この場合、最後のアドレスでない限り、またはマップがその場所でマップ解除されていない限り、マッピングの拡張は常に失敗します。

Linux で動作することを私が知っているアプローチは、複雑で、アーキテクチャ固有のものです。元のマッピングを読み取り専用に再マッピングし、新しい匿名マップを作成して、そこに古いコンテンツをコピーできます。 SIGSEGV が必要です ハンドラー (SIGSEGV 現在読み取り専用のマッピングに書き込もうとする特定のスレッドに対して発生するシグナル。これは数少ない回復可能な SIGSEGV の 1 つです。 これは、問題の原因となった命令を調べて (代わりに新しいマッピングの内容を変更して) シミュレートし、問題のある命令をスキップします。猶予期間の後、読み取り専用になった古いマッピングにアクセスするスレッドがなくなったら、マッピングを破棄できます。

不快感はすべて SIGSEGV にあります もちろんハンドラー。すべてのマシン命令をデコードしてそれら (または少なくともメモリに書き込む命令) をシミュレートできる必要があるだけでなく、新しいマッピングがまだ完全にコピーされていない場合はビジー待機する必要もあります。これは複雑で、完全に移植性がなく、非常にアーキテクチャ固有です..しかし可能です.


これは、5.7 カーネルで、MREMAP_DONTUNMAP と呼ばれる mremap(2) の新しいフラグとして追加されました。これにより、ページ テーブル エントリを移動した後、既存のマッピングがそのまま残ります。

https://github.com/torvalds/linux/commit/e346b3813067d4b17383f975f197a9aa28a3b077#diff-14bbdb979be70309bb5e7818efccacc8を参照してください


はい、できます。

mremap(old_address, old_size, new_size, flags) サイズ「old_size」の古いマッピングのみを削除します。そのため、「old_size」として 0 を渡すと、何もマップ解除されません。

注意:これは共有マッピングでのみ期待どおりに機能するため、そのような mremap() は以前に MAP_SHARED でマップされた領域で使用する必要があります。 mmap() フラグの「MAP_SHARED | MAP_ANONYMOUS」の組み合わせ。一部の非常に古い OS は「MAP_SHARED | MAP_ANONYMOUS」をサポートしていない場合がありますが、Linux では安全です。

MAP_PRIVATE 領域でこれを試すと、結果は memcpy() とほぼ同じになります。つまり、メモリ エイリアスは作成されません。しかし、それでも CoW 機構を使用します。あなたの最初の質問では、エイリアスが必要なのか、それとも CoW のコピーでもよいのかが明確ではありません。

更新:これを機能させるには、明らかに MREMAP_MAYMOVE フラグも指定する必要があります。


Linux
  1. Linuxがメインフレームに登場した経緯

  2. Linuxカーネルテストのライフサイクル

  3. Linuxでmvなしでファイルを移動する

  1. Linuxで上書きせずに同じファイルを読み書きしますか?

  2. Linux –なぜ新しいPidは古いものよりも小さいのですか?

  3. Linux –インタープリターバイナリを設定せずにスクリプトで機能を使用できますか?

  1. LinuxとUnix:違いは何ですか?

  2. Linuxlsコマンドをマスターする

  3. initrd イメージを作成せずに Linux カーネルを起動することはできますか?