以下は、Linux カーネル文書、Documentation/kmemleak.txt の kanda.motohiro@gmail.com による訳です。原文と同じ、GPL v2 で公開します。
カーネルメモリーリーク検出器
===========================
はじめに
------------
Kmemleak は、tracing garbage collector
(http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29#Tracing_garbage_collectors) のような方法で、カーネルメモリーのリークの可能性を検出する方法を提供します。ただし、孤児オブジェクトは解放されるのでなく、 /sys/kernel/debug/kmemleak で報告されるだけです。Valgrind tool (memcheck --leak-check) は、似たような方法を使って、ユーザー空間のアプリケーションのメモリーリークを検出します。
サポートされるアーキテクチャについては、 lib/Kconfig.debug の DEBUG_KMEMLEAK 依存をチェック下さい。
使い方
-----
CONFIG_DEBUG_KMEMLEAK in "Kernel hacking" を有効にしてください。カーネルスレッドが、10分毎(デフォルト)にメモリーをスキャンして、新しく見つかった参照されていないオブジェクトの数を表示します。メモリーリークの可能性のすべてを表示するには:
# mount -t debugfs nodev /sys/kernel/debug/
# cat /sys/kernel/debug/kmemleak
ただちにメモリースキャンを開始するには:
# echo scan > /sys/kernel/debug/kmemleak
メモリーリークの可能性のすべてを消去するには:
# echo clear > /sys/kernel/debug/kmemleak
/sys/kernel/debug/kmemleak をもう一度読むと、新しいリークが見えます。
孤児オブジェクトは、アロケートされた順にリストされます。リストの最初のオブジェクトのために、その後の他のオブジェクトが孤児と報告されることがあります。
/sys/kernel/debug/kmemleak ファイルに書き込むことで、メモリースキャンのパラメーターを実行時に変えることができます。以下がサポートされます。
off - kmemleak を無効にします。(元に戻せません。)
stack=on - タスクスタックのスキャンを有効。(デフォルト)
stack=off - 無効
scan=on - 自動メモリースキャンスレッドを開始(デフォルト)
scan=off - 停止
scan=<secs> - 自動メモリースキャン間隔を秒で与えます。
(デフォルトは 600, 0 は自動スキャンを停止。)
scan - メモリースキャンを開始します。
clear - 現在のメモリーリークの疑いのリストを消去します。現在報告されている参照されないオブジェクトをすべて、グレーとマークします
dump=<addr> - <addr> に見つかったオブジェクトの情報をダンプします。
ブート時に、"kmemleak=off" をコマンドラインに指定することで、kmemleak を無効にすることもできます。
kmemleak が初期化される前に、メモリーが確保、解放されることがあります。これらのアクションは、early log buffer に記録されます。このバッファの大きさは、 CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE オプションで設定されます。
基本アルゴリズム
---------------
kmalloc, vmalloc, kmem_cache_alloc およびその仲間を使ったメモリー確保は、追跡され、ポインターおよび長さやスタックトレースなどの追加の情報と共に prio search tree に記録されます。対応する解放関数呼び出しも追跡され、ポインターなどが前記データ構造から取り除かれます。
確保されたメモリーブロックは、その開始アドレスあるいはそのブロックの中の任意の位置を指すポインターが、メモリー(退避されたレジスターを含む)をスキャンしても見つからない時に、孤児であると判断されます。そのような場合、カーネルは、確保したブロックのアドレスを解放関数に与える手段がないということなので、ブロックはメモリーリークしたと言えます。
スキャンアルゴリズムの手順:
1. すべてのオブジェクトを白にします。(最後まで残った白オブジェクトは、孤児となります。)
2. データセクションとスタックから始めて、メモリースキャンをします。値を、 prio search tree に記録されているアドレスと比較します。
白オブジェクトへのポインターが見つかったら、オブジェクトはグレーリストに入ります。
3. グレーオブジェクトをスキャンして、アドレスを探します。(白オブジェクトがグレーになって、リストの最後に加わることもあります。)グレーリストが終わるまで続けます。
4. 残った白オブジェクトは、孤児となり、
/sys/kernel/debug/kmemleak で報告されます。
確保されたメモリーブロックによっては、ポインターがカーネルの内部的データ構造に記録されていることがあり、孤児である判断はできないことがあります。これを防ぐため、 kmemleak は、見つけられる必要のあるブロックアドレス範囲の内部を指す値を、独自にいくつか保持することができ、そのブロックが孤児とみなされないようにすることができます。 __vmalloc() は1つの例です。
特定のセクションを kmemleak でテストする
---------------------------------------
(大意)ブート直後など、警告がたくさんあるときは、クリアして、
# echo clear > /sys/kernel/debug/kmemleak
... テストしたいコードを動かして ...
# echo scan > /sys/kernel/debug/kmemleak
報告を出すとよいです。
# cat /sys/kernel/debug/kmemleak
Kmemleak API
------------
関数プロトタイプは include/linux/kmemleak.h ヘッダーにあります。
kmemleak_init - initialize kmemleak
kmemleak_alloc - notify of a memory block allocation
kmemleak_alloc_percpu - notify of a percpu memory block allocation
kmemleak_free - notify of a memory block freeing
kmemleak_free_part - notify of a partial memory block freeing
kmemleak_free_percpu - notify of a percpu memory block freeing
kmemleak_not_leak - mark an object as not a leak
kmemleak_ignore - do not scan or report an object as leak
kmemleak_scan_area - add scan areas inside a memory block
kmemleak_no_scan - do not scan a memory block
kmemleak_erase - erase an old value in a pointer variable
kmemleak_alloc_recursive - as kmemleak_alloc but checks the recursiveness
kmemleak_free_recursive - as kmemleak_free but checks the recursiveness
false positives/negatives に対処する
--------------------------------------
false negative とは、実際のメモリーリーク(孤児オブジェクト)であって、 kmemleak が報告しないものです。メモリースキャンで見つけたポインターが、そのオブジェクトを指しているためです。false negative を減らすために、 kmemleak_ignore, kmemleak_scan_area, kmemleak_no_scan and kmemleak_erase 関数があります。(上記を参照。)タスクのスタックは、false negative の量を増やすため、デフォルトではそれをスキャンしません。
false positive は、メモリーリーク(孤児)であると、誤って報告されるオブジェクトです。リークでないことがわかっているオブジェクトのために、kmemleak_not_leak 関数があります。kmemleak_ignore を使って、そのメモリーブロックが他へのポインターを持たないことを宣言すれば、スキャンされなくなります。
報告されるリークが、一時的であることもあります。特に、SMP システムの場合。ポインターが、CPU のレジスターやスタックに一時的に置かれるためです。MSECS_MIN_AGE (デフォルトで 1000) を使って、メモリーリークであると報告されるオブジェクトの最小の生存期間を指定することができます。
限界と欠点
-------------------------
主な欠点は、メモリー確保と解放の性能低下です。それ以外のペナルティを避けるために、メモリースキャンは、/sys/kernel/debug/kmemleak ファイルが読まれた時にだけ行われます。とはいえ、このツールは性能が最も重要な要件ではない場合のデバッグ用です。
アルゴリズムを単純にするために、 kmemleak は、ブロックのアドレス範囲の中の任意のアドレスを指す値を探してスキャンします。これにより、false negative が増えることがあります。しかし、本当のメモリーリークがいずれ明らかになるでしょうからよしとしましょう。
false negative のもうひとつの出所は、ポインターでない値に格納されているデータです。将来のバージョンでは、 kmemleak は、確保された構造体の、ポインターメンバーだけをスキャンすることが期待されます。これは、前記の多くの false negative ケースを解決するでしょう。
ツールは、false positive を報告することがあります。確保されたブロックを解放する必要のない場合( init_call 関数のケースなど)があります。また、ポインターが、通常の container_of マクロ以外の方法で計算される場合、あるいは、ポインターが、kmemleak がスキャンしない場所に格納されている場合などです。
ページ確保と ioremap は追跡されません。