組込み屋として知っておくべきLinuxのメモリアロケータに関するあれこれ

最近得た知見をメモしておきます。
このあたりの知識は基盤ソフトウェアをやってる人達にとっては常識なんだろうけど。。。


間違い,勘違い,実装による違い等ありそうですので,いつも以上に眉に唾して読んでください。突っ込みお待ちしています。
なおここでは,広くLinuxで使用されているglibcのメモリアロケータを想定しています。

スポンサーリンク

malloc()で確保したメモリを物理的に解放する

malloc()で確保したメモリはfree()で解放しても物理メモリが解放されないケースがあります。

MMAP_THRESHOLD (デフォルト128KB) 未満のmallocの場合
解放済メモリリストを検索し,指定のサイズの領域が確保できた場合はそのアドレスを返します。

解放済メモリリストから,指定サイズの領域を確保できなかった場合はヒープから確保します。

ヒープ領域が足りない場合はsbrk()でヒープを拡張します。

free()されたメモリは解放済みメモリリストに登録されるため,物理的には解放されません

MMAP_THRESHOLD (デフォルト128KB) 以上のmallocの場合
内部でmmap()を用いて指定サイズの領域を確保し,そのアドレスを返します。

mmap()とは,メモリを確保するためのインタフェースです。mmap()には以下の特徴があります。

  • 確保したメモリはmunmap()で物理メモリを解放する。malloc()内でmmap()された領域は,free()する時にmunmap()される。
  • 4KB単位でしかメモリの確保ができない。
  • mmap()で確保したメモリはゼロ初期化は不要(ゼロ保証されている)

したがって,free()されたメモリは解放済みメモリリストには登録されず,物理的に解放されます

sleep状態で常駐するプログラムが多い組込機器では,MMAP_THRESHOLD未満のmallocの動作が問題となります。
(e.g ある機能を使用した後にメモリの使用量が機能の使用前と同じにならない)

ただしメモリ確保の性能は malloc > mmap なので,なんでもかんでもmmapすれば良いというわけでもありません。

MMAP_THRESHOLDの動的な変化を抑制する

MMAP_THRESHOLDは, 128KB以上512KB未満のmalloc()free()を行うことによって,そのサイズまで上限が引き上げられます。
これにより, 以降のmalloc()/free()では引き上げられたサイズ未満までは物理メモリが解放されなくなります。

この動作を抑制するため, mallopt()というインタフェースが存在します。
mallopt()MMAP_THRESHOLDの値を設定することで,動的閾値変更を無効化し,malloc()の閾値を変更できます。

これにより,大きなサイズのmalloc()ではmmap()が呼ばれるようになり,free()で物理的にメモリが解放されるようになります。

ページキャッシュを解放する

通信用バッファなど,単方向のメモリアクセスをする場合は,ページキャッシュを解放することで物理メモリの空き容量増加が期待できます。ページキャッシュの解放はmadvise()MADV_DONTNEEDを指定します。これでカーネルにリソースはもう解放しても良い,とアドバイスすることができます。ただし,このアドバイスを受け入れるかどうかはカーネルに任されます。

この方法は静的メモリを解放する場合に有効です。静的メモリの場合,一旦物理メモリが割り当てられると,(私の知る限りでは)他の方法で物理メモリの解放はできません。

動的メモリの確保と解放は上述の通り,それぞれ対応するAPIがありますので,このAPIを使用すべきではありませんが、メモリを解放したいが「そのメモリ空間にアクセスしないこと」を保証できない場合,madvise()で解放することも可能です。この方法の場合,物理メモリを解放するだけで論理メモリは解放しないため,アクセスがあった場合は0が読まれることになります。ただし,madvise()で解放するメモリは4KBバウンダリから4KB単位であることに注意しなければなりません。解放したメモリの中にmadvise()する領域が包含されるようにしないと,予期しない動作を引き起こします。

不要にゼロ初期化しない

上述の通り,MMAP_THRESHOLDを越える量のメモリをmalloc()で確保すると,mmap()が呼ばれますが,この時mmap()は物理メモリを割り当てておらず,指定サイズの論理アドレスのみが割り当てられています。

この領域に対して読み書きをした場合,4KB単位で物理メモリが割り当たり,この物理メモリはfree()もしくはmunmap()されるまでは解放されません。また、上述の通り,この領域はゼロ保証されています。

したがって,malloc()した後memset()でゼロ初期化すると,ゼロ保証されている領域をゼロ初期化するだけでなく,本来であれば使う必要のなかった領域まで確保する可能性があります。

参考

少し古いですがすごく参考になりました
malloc(3)のメモリ管理構造 | VA Linux System Japan 株式会社

スポンサーリンク

フォローする