Linuxでは限られたメモリを最大限に活かし、仮想メモリなどの仕組みを使って最大限のパフォーマンスを発揮できるようになっています。その反面、実際にどれくらいの物理メモリを消費しているのかといった情報が分かりにくく、開発者にとっては例えばメモリリークの特定が難しい場合もあります。

そこで今回はLinuxのメモリの使用量について確認する方法を紹介します。さらに、動的テストツールによる動的解析と組み合わせて使用することにより、ターゲット機器が実行された際のメモリ使用量の監視に加えて、実行されていた処理と照らし合わせて解析する方法を紹介します。

 

Linuxのメモリ管理について

 
proc/meminfoで見るメモリ情報

Linux上でメモリの使用状況を取得するコマンドは「free」「top」「ps」などがありますが、これらは「proc/meminfo」の情報を使用しています。「proc/meminfo」を直接参照することで、システム全体の詳細なメモリの使用状況を把握することが可能です。実際に情報を取得すると以下のようにメモリに関する様々な情報が出力されます。なお、カーネルのバージョンは4.9です。

 
どれくらいのメモリを使用しているのか

この出力の「MemTotal」から「MemAvailable」を差し引いた値が、現在ターゲットとするシステムが使用しているメモリサイズです。

「MemTotal」がターゲット機器の物理メモリのサイズです。値を確認してみると搭載されているRAMのサイズと異なりますが、搭載しているRAMのサイズからカーネルのバイナリコードなどを差し引いた値となっています。詳細な処理はアーキテクチャによって異なりますが、ターゲット機器を起動した際に計算されます。起動後、ブート処理で使用した後に使われないページのサイズなども足し合わされ、その後は変化しません。

「MemAvailable」とは、その名の通り「使用可能なメモリサイズ」で「free」コマンドの「available」フィールドと同じです。この値はキャッシュや再利用可能なメモリ領域を考慮した上で、スワップすることなく使用できるサイズを表しています。カーネルのコードで実際に行われている計算では、カーネル内部で管理しているLRU(Least Recently Used = ページに割り当てられたフレームを解放するため、直近で最も参照頻度が少ないものを選ぶカーネルのアルゴリズム)のリストからそのサイズを取得したり、「zone」というページの分割単位から「wmark_low(=watermarkという、この値を下回るとカーネルがメモリ不足と認識する危険水域)」を取得したりしています。

 

メモリ使用量の監視方法

これらの値をアプリケーションから定周期で参照することでメモリ使用量の監視を行います。メモリの使用量の計算はカーネル側で行う必要があるため、デバイスドライバを用意してその中で計算処理を行います。

 
メモリ使用量の取得処理

カーネル側では、「fs/proc/meminfo.c」の「meminfo_proc_show」というLinuxカーネル内の関数を参考にして、「MemTotal」と「MemAvailable」の値をデバイスドライバで取得します。今回はDT+のログ出力にも使用しているprocファイルシステムを使っており、「mem_trace_read」関数がメモリ使用量を計算している箇所になります。この処理はカーネルのバージョンによって異なり、以下の例はVer.4.6以降に対応した処理です。

/* メモリ使用量の取得 */
static ssize_t mem_trace_read(struct file *fp, char __user *buf, size_t count, loff_t *data)
{
	struct sysinfo i;
	long used_memsize;
	
	/* ページ数⇒キロバイトに変換 */
	#define K(x) ((x) << (PAGE_SHIFT - 10))		
	
	/* メモリの使用量を取得 */
	si_meminfo(&i);
	used_memsize = K(i.totalram - si_mem_available());
	copy_to_user((void*)buf, &used_memsize, sizeof(long));
	return 0;
}

static const struct file_operations mem_trace_proc_fops = {
	.read = mem_trace_read,
};

static int mem_trace_init(void)
{
	/* モジュールロード時の処理 */
}
static void dt_mem_trace_exit(void)
{
	/* モジュールアンロード時の処理 */
}

このようにモジュール化しておくことで、処理を追加したり変更する際にいちいちカーネルをビルドしなおす必要がないため便利です。また必要に応じてロード・アンロードできるため、デバッグ時のみロードしておくといった柔軟な使い方ができるようになります。

おさらい:ページとは?

コード上に「ページ数⇒キロバイトに変換」とありますが、ページとは仮想メモリ空間の固定長の分割単位です。仮想メモリ空間はこの固定長のブロック単位で物理メモリに割り当てられます。この固定長のサイズは一般的に4KByteがサポートされておりますが、CPUごとに異なります(カーネルコンフィグより選択することが可能)。これに対して物理メモリは「フレーム」という分割単位で領域を確保して、ページを割り当てていきます。データの読み書きが発生するページに対して公平・効率的にフレームが割り当てられるように、Linuxカーネルでは物理メモリの割り当てをコントロールしています。

デバイスドライバを呼び出すアプリケーション側のコードは以下のようになります。1sec周期で上記のデバイスドライバの処理を呼び出すようにしています。

#define MEM_TRACE_DRV    "/proc/mem_trace_proc"

void* thread_mem_output(void *state)
{
    struct timespec start, end;
    long memsize;
    while(!thread_kill)
    {
        clock_gettime(CLOCK_MONOTONIC, &amp;start); 

        /* メモリ使用量の取得 */
        read(dat_file, &amp;memsize, sizeof(memsize));

        clock_gettime(CLOCK_MONOTONIC, &amp;end);
        nDelay(0, 1000000000 - (end.tv_nsec - start.tv_nsec));  /* 1sec周期 */
    }
    printf("thread_mem_output exit");
    pthread_exit(NULL);
}

 
実際に実行してみる

実際に上記のカーネルをビルドしターゲットへと書き込み、アプリケーションを実行してみます。そうすると以下のようにアプリケーションを実行しながらリアルタイムにメモリの使用量を確認できます。

これにより、どういう操作をしたときにメモリの使用量が増加したのか、そして確保されたメモリが解放されたのか、といったことが分かるようになります。開発者としては、そのときに実行されていた処理や変数値といったソースコードに関する情報が知りたいところで、これらはズバリ「動的テストツール DT+」の得意とするところです。DT+を併用することでそういった情報と、メモリの使用状況を照らし合わせて確認できます。

 
DT+でリアルタイムに監視する
ハートランド・データといえば動的解析ツールDT+!
組込み機器の実機を動かし、「ナマの挙動」を解析する動的解析。
DT+を使うと、ソフトどころかハード側の挙動まで全部ひと手間で解析できます。
DT+の詳細はこちら

「動的テストツール DT+」の詳細は上記のページに任せますが、ターゲットを実際に動作させ、その際の実行処理・経路や処理時間を解析できるツールです。豊富な解析機能によりデバッグや不具合解析を大幅に効率化できます。変数値を出力する機能もありますので、今回はこの機能を使ってメモリの使用量を出力します。

DT+を使用することによって「どの関数が実行されていた」「どの分岐に入った」といった情報が解析できます。これをメモリの使用量と合わせて確認することで、メモリ使用量がその値になった際に実行されていた処理を効率的に確認できます。DT+には変数値をグラフィカルに確認するための機能も用意されており(下図左側)、メモリの使用量の変化の確認もできます。今回は定周期でメモリの確保を行っていましたが、その様子がグラフから分かります。

ログのリストもグラフィカルに表示する機能がありますので(上図右側)、実行経路を俯瞰した形で確認できます。前後の処理の関連性を関数・スレッド・プロセス単位で確認できますので、処理に対する理解を深め現象を正確に把握することに役立ちます。

 
メモリの使い過ぎを自動で検出する方法

DT+を使ったメモリ監視による利点をもうひとつ。それはメモリ使用量があらかじめ設定したスレッシュを超えた際に自動でアラートしてくれる点です。スレッシュを超えた値がないかどうか、いちいち手動で値を確認する必要がなくなるため確認の手間がグンと減り見落としもなくなります。さらに、この機能を使うとスレッシュを超えたときのログを自動で抽出してくれるため、本当に見たいログの箇所(=どういったときにスレッシュを超えてしまったのか)をすぐに確認できます。

この設定は過去に取得したログでも適用することが可能で、同様の事象が発生していないかどうか、またアラートの原因となっている処理のパターンは同一かどうか、などの確認を行えます。

 

まとめ

このような仕組みをあらかじめ検討しておくことで、開発者各人がコード変更時やリリース時などに確認できるようになるので、予期せぬ不具合の流出を防ぐことにつながります。

また、DT+を使って解析することによって、メモリの情報だけでなく、すでに担当者がいないため情報が不足していて時間もかかるような厄介なケースでも、大きく工数をロスすることなく解析ができるようになります。短納期で高機能、開発スピードがより一層求められる昨今、デバッグ手法を磨くためのヒントとしてみなさまの参考にしていただければと思います。


【 無料セミナー 】
動的テストツールDT+ デモウェビナー

ソフトウェア開発者のための動的テストツール「DT+」をご紹介するセミナーです。
ソースコードの実行によりログを収集し、多彩な解析機能によりソフトウェアの動作をこまかく見える化。たった数クリックの解析で、関数遷移や変数の変動、カバレッジをグラフィカルに表示します。本セミナーでは、そんなDT+の導入手法から実際の解析の様子まで、基本的な使い方をデモンストレーションいたします。

お申し込みはこちら!