Linux Memory Overcommit

かなり昔ですが、或日さんのSolaris8におけるmemory over commitの話題に触発され、Linuxの場合について調べた事があります。正しさは保証しませんが当時のメモを貼っておきます。

memory overcommit?


メモリ資源の非常に限られた環境でLinuxを使用しているとよく遭遇する現象なのだが、kernel 2.4までのLinuxでは、"mallocが成功を返したにもかかわらず、その確保した筈のメモリ領域にアクセスすると、SIGKILLによってkernelから強制終了させられてしまう" という現象が 発生することがある。これは、「全プロセスがnew/mallocしたメモリの総量」に対して物理メモリ量が少なすぎる瞬間に於いては常に発生し得る。


何に困っているかといえば、C++でオブジェクトをnewした時に、メモリの枯渇をstd::bad_alloc例外送出という形で教えてもらえないことだ。メモリ不足をユーザプログラムで検知できるなら、それなりの縮退運転モードに入れるのだが、確保成功と思いきやいきなりKILLされるのではなすすべもない。


デフォルト設定の kernel 2.4 では、ユーザがnew/mallocでメモリを要求した時、kernelはその要求(brkシステムコール)を受け入れるかどうかを「経験的手法」で判断する。あまりに大きな確保要求は拒絶するが、そうでない場合には、"現在未使用の物理メモリ(以下ページフレームと呼ぶ)+未使用のswap" と要求のメモリ量を厳密には見比べずに許可不許可を決めるという手法だ。この方法をとると、冒頭で述べた「確保できたが使えない」という状況が発生し得るのは自明で、実際にそういう状況になると、kernelがもっとも適切と判断したプロセスをSIGKILLするのである。


Solarisなどに比べるとある意味いい加減な手法だが、(おそらく)パフォーマンスの面などで良いこともあるらしく、LKMLなどでも度々終わらない議論になっているようだ。


なお「経験的手法」の精度だが、手元の試験(2.4.22/x86, 2.4.20/mips)では、「4k程度のメモリ確保」「確保領域を特定のビットパターンでfill」という操作を何万回繰り返してもnewは成功し続け、ただの一度もstd::bad_allocは送出されなかった。プロセスは最後にkernelにKILL -9され、"Out of Memory: Killed process XXXXX (process_name)" というsyslogだけが残った。まぁ普通はこんな恣意的な操作が行われる事は考えにくいので、これはこれで良いのかもしれない。

kernelの設定を変える


さて、kernelが上で述べた「経験的(heuristic)手法」をとるのは、 /proc/sys/vm/overcommit_memory の値が "0" に設定されている場合であり、また kernel 2.4 ではこれがデフォルトである。逆に言うと、この値を弄る事でメモリ確保要求へのkernelの挙動を変化させることができる。


RHEL Reference Manual や /usr/src/linux/Documentations/vm/overcommit-accounting によると、overcommit_memory は3つの値をとることができると記述されており、次の通りである:

0 The kernel performs heuristic memory overcommit handling, by estimating the amount of memory available and failing requests that are blatantly invalid. Unfortunately, since memory is allocated using a heuristic rather than a precise algorithm, this setting can sometimes allow overloading the memory available on a system. This is the default setting.
1 The kernel performs no memory overcommit handling. Under this setting, the potential for memory overload is increased, but so is performance for memory intensive tasks (such as those executed by some scientific software).
2 The kernel fails requests for memory that add up to all of swap plus the percent of physical RAM specified in /proc/sys/vm/overcommit_ratio. This setting is best for those who desire less risk of memory overcommitment.
3〜5 (一時存在したが廃止された)
"0" はすでに述べたモードである。


"1" は kernel 2.0 までのデフォルト(そして固定値)であり、overcommit handling をしないモードである。つまり、リニアアドレス空間にマップできないような馬鹿げた量のメモリを確保しようとしたのでもない限り、常にnew/mallocは成功で戻る。


"2" は巷で "strict non-overcommit mode" などと呼ばれているもので、全プロセスの現在の合計malloc量をkernelが覚えているモードだ。さらに、そのmalloc量の上限を /proc/sys/vm/overcommit_ratio で設定できる。具体的には次の単純な数式だ。

malloc_limit[MB] 
   = swap領域のサイズ[MB] + (物理メモリ量[MB] * overcommit_ratio / 100)

まとめ


今まで述べた事柄により、

メモリが逼迫した状況でも、memory overcommit によるプロセスの突然死を起こしたくない状況では、/proc/sys/vm/overcommit_memory を "2" にし、かつ overcommit_ratio を調整して "malloc_limit"値 が物理メモリ量を超えないようにすれば良い

という結論が導かれる。swapを持たない組み込み機器などでは、単純にovercommit_ratio を100に近い値にしておけば良い筈。


た・だ・し、overcommit_memory = 2 に設定できるのは kernel 2.6 以降のみ である。一部のディストロの kernel 2.4 には 2.6 からの backport patch が当たっているものの、overcommit_ratio の設定が効かなかったりするらしいPostgreSQLのドキュメント の§16.5.3が詳しい)。2.4がまだまだ主流の現在では注意を要する。


さて、kernel 2.6 を使えなかったり、kernel の設定をいじることができない環境ではどうするか。まず思いつくのがsetrlimit(2)によるプロセス毎の資源量の制限だろう。ulimit コマンド等で使用可能なメモリ量 のソフトリミット、ハードリミットを制限してプロセスを起動するなどして実践できる。


あるいは、例えばOpenSSLなどが行っているような、ユーザプロセスでのメモリの自主管理となろうか。


しかし、「bad_alloc飛んでくれなきゃイヤ!!」などと駄々をこねるのは、組み込みなのにメモリ使用量の設計をろくにしていない事が理由だったりしがちで、そういうときはメモリ使用量の自主管理など実質的に不可能だったりもします :-p


ちなみに、ulimitで縛るときは max memory size (-m) や max locked memory (-l)はうまく効かないことがある。というか、これらは使用中のページフレーム数(RSS)で縛るんで、VMの機構を承知していないと期待通り動かないでしょう。主観ですが、RSSで縛るより使用可能なリニアアドレスの量で縛る (-v) で縛る方が直感的で、アプリケーションの設計もしやすいように思います。