3

問題

アプリケーションでのマイナーなページフォールトの発生を減らしたりなくしたりしようとしている過程で、私は紛らわしい現象を発見しました。つまり、ページフォルトを防ぐために十分な措置を講じたと思っていたにもかかわらず、同じアドレスへの書き込みに対してマイナーなページフォルトを繰り返しトリガーしています。

バックグラウンド

hereのアドバイスに従って、mlockall現在および将来のすべてのページをメモリにロックするように呼び出しました。

私の元のユースケース (かなり大きな配列が関係していました) では、こちらのアドバイスに従って、すべての要素 (または少なくともすべてのページ) に書き込むことにより、データを事前にフォールトしました。RT パッチを適用したカーネルを実行しているユーザーを対象としたアドバイスであることは理解していますが、書き込みを強制して COW を阻止し、ページングを要求するという一般的な考え方は引き続き適用できるはずです。

mlockallマイナーなページ フォールトを防ぐために使用できると考えていました。マニュアル ページはメジャー フォールトが発生しないことを保証しているように見えますが、他のさまざまなリソース (上記など) では、マイナー ページ フォールトを防ぐためにも使用できると述べています。

カーネルのドキュメントもこれを示しているようです。たとえば、unevictable-lru.txtpagemap.txtは、mlock()'ed ページは不可避であるため、再利用には適していないと述べています。

それにもかかわらず、私はいくつかのマイナーなページフォールトを引き起こし続けました.

問題を説明するために、非常に簡素化された例を作成しました。

#include <sys/mman.h> // mlockall
#include <stdlib.h> // abort

int main(int , char **) {
  int x;

  if (mlockall(MCL_CURRENT | MCL_FUTURE)) abort();

  while (true) {
    asm volatile("" ::: "memory"); // So GCC won't optimize out the write
    x = 0x42;
  }
  return 0;
}

ここでは、同じアドレスに繰り返し書き込みます。cat /proc/[pid]/status | awk '{print $10}'初期化が完了してから長い間、マイナーなページフォールトが発生し続けることは (たとえば を介して) 簡単にわかります。

pfaults.stpに含まれているスクリプトの修正版*を実行してsystemtap-doc、各ページ フォールトの時刻、フォールトをトリガーしたアドレス、フォールトをトリガーした命令のアドレス、メジャー/マイナー、読み取り/書き込みを記録しました。スタートアップ および からの最初の障害の後mlockall、すべての障害は同一xでした。

連続するページフォールトの間隔には、顕著なパターンが見られます。ある特定の実行では、間隔は秒単位でした: 2, 4, 4, 4.8, 8.16, 13.87, 23.588, 40.104, 60, 60, 60, 60, 60, 60, 60, 60, 60, ... これは (ほぼ) 指数関数的なバックオフのようで、絶対的な上限は 1 分です。

分離された CPU で実行しても影響はありません。より高い優先順位で実行することもありません。ただし、リアルタイムの優先度で実行すると、ページフォールトがなくなります。

質問

  1. この動作は予期されたものですか?
    1a. タイミングの説明は?
  2. これを防ぐことは可能ですか?

バージョン

3.13.0-24-genericカーネルと Systemtap のバージョンで Ubuntu 14.04 を実行しています2.3/0.156, Debian version 2.3-1ubuntu1 (trusty)。追加のフラグなしでコンパイルされたコードgcc-4.8。ただし、最適化レベルは問題ではないようです (asm volatileディレクティブがそのまま残っている場合。そうでない場合、書き込みは完全に最適化されます)。

stap関連性があれば、詳細 (正確なスクリプト、元の出力など) を喜んで含めます。


*実際には、vm.pagefaultカーネルと systemtap の私の組み合わせでは、カーネルの関数に存在しなくなった変数を参照していたため、プローブが壊れていましたhandle_mm_faultが、修正は簡単でした)

4

2 に答える 2

2

@fcheがTransparent Huge Pagesについて言及したことで、私は正しい方向に進みました。

質問でリンクしたカーネルのドキュメントを不注意に読んでも、カーネルがページを新しいページ フレームに移行するのを妨げないmlockことがわかります。実際、mlocked pages の移行に特化したセクション全体があります。したがって、単純に呼び出すだけでは、マイナーなページフォールトが発生しないという保証はありません。mlock()

少し遅れて、この回答は同じ箇所を引用しており、私の質問に部分的に回答していることがわかります。

カーネルがページを移動する理由の 1 つはメモリ圧縮です。これにより、カーネルはページの大きな連続ブロックを解放して、「巨大なページ」を割り当てることができます。Transparent huge page は簡単に無効にすることができます。たとえば、この回答を参照してください。

私の特定のテスト ケースは、 3.13 カーネルで導入されたいくつかの NUMA バランス変更の結果でした。

そこにリンクされているLWNの記事を引用する:

スケジューラは、各プロセスのアドレス空間を定期的にスキャンし、現在 RAM に常駐しているページへのすべてのアクセス許可を取り消します。次に影響を受けるプロセスがそのメモリにアクセスしようとすると、ページ フォールトが発生します。スケジューラはその障害をトラップし、問題のページへのアクセスを復元します...

スケジューラーのこの動作は、特定のノードを明示的に使用するようにプロセスの NUMA ポリシーを設定することで無効にすることができます。numactlこれは、コマンド ライン (例: ) またはライブラリnumactl --membind=0の呼び出しを使用して実行できます。libnuma

編集: sysctlのドキュメントには、NUMA の分散に関して明示的に記載されています。

ターゲット ワークロードが既に NUMA ノードにバインドされている場合は、この機能を無効にする必要があります。

これはで行うことができますsysctl -w kernel.numa_balancing=0

ページ移行の原因は他にもあるかもしれませんが、私の目的にはこれで十分でした。

于 2014-06-03T23:08:15.933 に答える