3.7. memory

memory サブシステムは、cgroup 内のタスクによって使用されるメモリーリソースの自動レポートを生成し、他のタスクによるメモリー使用の上限を設定します。
memory.stat
以下の表に記載した、広範囲なメモリーの統計をレポートします。

表3.2 memory.stat によりレポートされる値

統計 説明
cache tmpfs (shmem) を含むページキャッシュ (バイト単位)
rss tmpfs (shmem) を含まない匿名のスワップキャッシュ (バイト単位)
mapped_file tmpfs (shmem) を含むメモリーマップドファイルのサイズ (バイト単位)
pgpgin メモリー内へページされたページ数
pgpgout メモリーからページアウトされたページ数
swap スワップの使用量 (バイト単位)
active_anon tmpfs (shmem) を含む、アクティブな最長時間未使用 (LRU) 一覧上の匿名のスワップキャッシュ (バイト単位)
inactive_anon tmpfs (shmem) を含む、非アクティブ LRU 一覧上の匿名のスワップキャッシュ (バイト単位)
active_file アクティブ LRU 一覧にある、ファイルと関連づけされたメモリー (バイト単位)
inactive_file 非アクティブ LRU 一覧にある、ファイルに関連付けされたメモリー (バイト)
unevictable 再生不可のメモリー (バイト単位)
hierarchical_memory_limit memory cgroup が含まれる階層のメモリー制限 (バイト単位)
hierarchical_memsw_limit memory cgroup が含まれる階層のメモリーとスワップの制限 (バイト単位)

また、これらのファイルの中で、hierarchical_memory_limit および hierarchical_memsw_limit 以外のファイルには、それぞれ、total_ というプレフィックスの付いた対応ファイルがあり、cgroup についてだけでなく、その子グループについてもレポートします。たとえば、swap は cgroup によるスワップの使用量をレポートし、total_swap は cgroup とその子グループによるスワップの使用量をレポートします。
memory.stat によってレポートされた値を解析する際には、さまざま統計が相互に関連している点に注意してください。
  • active_anon + inactive_anon = 匿名メモリー + tmpfs のファイルキャッシュ + スワップキャッシュ
    したがって、active_anon + inactive_anonrss となります。これは、rsstmpfs が含まれないのが理由です。
  • active_file + inactive_file = cache - size of tmpfs
memory.usage_in_bytes
cgroup 内のプロセスによる現在のメモリー総使用量をレポートします (バイト単位)。
memory.memsw.usage_in_bytes
cgroup 内のプロセスによる現在のメモリー使用量と使用済みスワップ領域の和をレポートします (バイト単位)。
memory.max_usage_in_bytes
cgroup 内のプロセスによるメモリー最大使用量をレポートします (バイト単位)。
memory.memsw.max_usage_in_bytes
cgroup 内のプロセスによるスワップメモリー最大使用量と使用済みスワップ領域をレポートします (バイト単位)。
memory.limit_in_bytes
ユーザーメモリーの最大値 (ファイルキャッシュを含む) を設定します。単位が指定されていない場合、その値はバイト単位と解釈されますが、より大きな単位を示すサフィックスを使用することが可能です (キロバイトには k または K、メガバイトには m または M、ギガバイトには g または G)。
root cgroup を制限するのには、memory.limit_in_bytes は使用できません。値を適用できるのは、下位階層のグループに対してのみです。
memory.limit_in_bytes-1 と書き込み、現行の制限値を削除します。
memory.memsw.limit_in_bytes
メモリーとスワップ使用量の合計の最大値を設定します。単位が指定されていない場合、その値はバイト単位と解釈されますが、より大きな単位を示すサフィックスを使用することが可能です (キロバイトには k または K、メガバイトには m または M、ギガバイトには g または G)。
root cgroup を制限するのに、memory.memsw.limit_in_bytes は使用できません。値を適用できるのは、下位階層のグループに対してのみです。
memory.memsw.limit_in_bytes-1 と書き込み、現行の制限値を削除します。

memory.memsw.limit_in_bytes および memory.limit_in_bytes パラメーターの設定

memory.limit_in_bytes パラメーターは、memory.memsw.limit_in_bytes設定する前に 設定しておくことが重要となります。逆の順序で設定を試みると、エラーが発生します。これは、memory.memsw.limit_in_bytes を使用できるようになるのが、(memory.limit_in_bytes で事前に設定されている) メモリー制限をすべて使い切った後のみであるためです。
次の例を検討してください: 特定の cgroup に対して memory.limit_in_bytes = 2Gmemory.memsw.limit_in_bytes = 4G と設定すると、cgroup 内のプロセスが 2 GB のメモリーを割り当てることが可能となり、それを使い果たすと、さらに 2 GB のスワップのみを割り当てます。memory.memsw.limit_in_bytes パラメーターはメモリーとスワップの合計を示しています。memory.memsw.limit_in_bytes パラメーターが設定されていない cgroup 内のプロセスは、(設定されているメモリーの上限を消費した後に) 使用可能なスワップをすべて使い果たしてしまい、空きスワップがなくなるために Out Of Memory (OOM) の状態を引き起こす可能性があります。
また、/etc/cgconfig.conf ファイルで memory.limit_in_bytesmemory.memsw.limit_in_bytes のパラメーターを設定する順序も重要です。この設定の正しい例は以下のとおりです。
memory {
    memory.limit_in_bytes = 1G;
    memory.memsw.limit_in_bytes = 1G;
}
memory.failcnt
memory.limit_in_bytes に設定されているメモリーの上限値に達した回数をレポートします。
memory.memsw.failcnt
memory.memsw.limit_in_bytes に設定されているメモリーとスワップ領域の合計が上限に達した回数をレポートします。
memory.force_empty
0 に設定されている場合には、cgroup 内のタスクによって使用される全ページのメモリーを空にします。このインターフェイスは、cgroup がタスクを持たない時にのみ使用できます。メモリーを解放できない場合は、可能ならば 親 cgroup に移動されます。cgroup を削除する前には、memory.force_empty を使用して、未使用のページキャッシュが親 cgroup に移動されないようにしてください。
memory.swappiness
ページキャッシュからページを再生する代わりに、カーネルがこの cgroup 内のタスクで使用されるプロセスメモリーをスワップアウトする傾向を設定します。これはシステム全体用に /proc/sys/vm/swappiness 内に設定されているのと同じ傾向で、同じ方法で算出されます。デフォルト値は 60 です。これより低い値を設定すると、カーネルがプロセスメモリーをスワップアウトする傾向が低減します。また 100 以上に設定すると、カーネルはこの cgroup 内のプロセスのアドレス領域となる部分のページをスワップアウトできるようになります。
0 の値に設定しても、プロセスメモリーがスワップアウトされるのを防ぐことはできない点に注意してください。グローバル仮想メモリー管理ロジックは、cgroup の値を読み取らないため、システムメモリーが不足した場合に、依然としてスワップアウトが発生する可能性があります。ページを完全にロックするには、cgroup の代わりに mlock() を使用してください。
以下にあげるグループの swappiness は変更できません。
  • /proc/sys/vm/swappiness に設定された swappiness を使用している root cgroup
  • 配下に子グループがある cgroup
memory.use_hierarchy
cgroup の階層全体にわたって、メモリー使用量を算出すべきかどうかを指定するフラグ (0 または 1) が含まれます。有効 (1) となっている場合、メモリーサブシステムはメモリーの上限を超過しているプロセスとその子プロセスからメモリーを再生します。デフォルト (0) では、サブシステムはタスクの子からメモリーを再生しません。
memory.oom_control
cgroup に対して Out of Memory Killer を有効化/無効化するフラグ (0 または 1) が含まれています。これを有効にすると (0)、許容量を超えるメモリーを使用しようとするタスクは OOM Killer によって即時に強制終了されます。OOM Killer は、memory サブシステムを使用するすべての cgroup でデフォルトで有効になっています。これを無効にするには、memory.oom_control ファイルに 1 と記載します。
~]# echo 1 > /cgroup/memory/lab1/memory.oom_control
OOM Killer が無効になると、許容量を超えるメモリーを使用しようとするタスクは、追加のメモリーが解放されるまで一時停止されます。
memory.oom_control ファイルは、現在の cgroup の OOM ステータスも under_oom エントリにレポートします。cgroup がメモリー不足の状態で、その cgroup 内のタスクが一時停止されている場合には、under_oom エントリで値が 1 とレポートされます。
memory.oom_control ファイルは、通知 API を使用して OOM 状態の発生をレポートすることができます。詳しくは、「通知 API の使用」 および 例3.3「OOM の制御と通知」 を参照してください。

3.7.1. 使用例

例3.3 OOM の制御と通知

以下の例は、cgroup 内のタスクが許容量を超えるメモリーの使用を試みた場合に OOM Killer がどのように対応し、通知ハンドラーが OOM 状態のどのようにレポートするかを示した実例です。
  1. memory サブシステムを階層に接続し、cgroup を作成します。
    ~]# mount -t memory -o memory memory /cgroup/memory
    ~]# mkdir /cgroup/memory/blue
  2. blue cgroup 内のタスクが使用できるメモリーを 100 MB に設定します。
    ~]# echo 104857600 > memory.limit_in_bytes
  3. blue ディレクトリに移動して、OOM Killer が有効になっていることを確認します。
    ~]# cd /cgroup/memory/blue
    blue]# cat memory.oom_control
    oom_kill_disable 0
    under_oom 0
    
  4. 現在のシェルプロセスを blue cgroup の tasks ファイルに移動し、このシェルで起動したその他すべてのプロセスが自動的に blue cgroup に移動するようにします。
    blue]# echo $$ > tasks
  5. ステップ 2 で設定した上限を超える大容量のメモリーを割り当てようとするテストプログラムを起動します。blue cgroup の空きメモリーがなくなるとすぐに OOM Killer がテストプログラムを強制終了し、標準出力に Killed をレポートします。
    blue]# ~/mem-hog
    Killed
    
    以下は、このようなテストプログラムの一例です。[5]
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #define KB (1024)
    #define MB (1024 * KB)
    #define GB (1024 * MB)
    
    int main(int argc, char *argv[])
    {
    	char *p;
    
    again:
    	while ((p = (char *)malloc(GB)))
    		memset(p, 0, GB);
    
    	while ((p = (char *)malloc(MB)))
    		memset(p, 0, MB);
    
    	while ((p = (char *)malloc(KB)))
    		memset(p, 0,
    				KB);
    
    	sleep(1);
    
    	goto again;
    
    	return 0;
    }
    
  6. OOM Killer を無効にし、テストプログラムを再度実行します。今回は、テストプログラムが一時停止の状態のままとなり、追加のメモリーが解放されるのを待機します。
    blue]# echo 1 > memory.oom_control
    blue]# ~/mem-hog
  7. テストプログラムが一時停止されている間は、cgroup の under_oom 状態が変わり、空きメモリーが不足していることを示している点に注意してください。
    ~]# cat /cgroup/memory/blue/memory.oom_control
    oom_kill_disable 1
    under_oom 1
    
    OOM Killer を再度有効にすると、テストプログラムは即時に強制終了されます。
  8. すべての OOM 状態についての通知を受信するためには、「通知 API の使用」 に記載したようにプログラムを作成してください。以下はその一例です[6]
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/eventfd.h>
    #include <errno.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    static inline void die(const char *msg)
    {
    	fprintf(stderr, "error: %s: %s(%d)\n", msg, strerror(errno), errno);
    	exit(EXIT_FAILURE);
    }
    
    static inline void usage(void)
    {
    	fprintf(stderr, "usage: oom_eventfd_test <cgroup.event_control> <memory.oom_control>\n");
    	exit(EXIT_FAILURE);
    }
    
    #define BUFSIZE 256
    
    int main(int argc, char *argv[])
    {
    	char buf[BUFSIZE];
    	int efd, cfd, ofd, rb, wb;
    	uint64_t u;
    
    	if (argc != 3)
    		usage();
    
    	if ((efd = eventfd(0, 0)) == -1)
    		die("eventfd");
    
    	if ((cfd = open(argv[1], O_WRONLY)) == -1)
    		die("cgroup.event_control");
    
    	if ((ofd = open(argv[2], O_RDONLY)) == -1)
    		die("memory.oom_control");
    
    	if ((wb = snprintf(buf, BUFSIZE, "%d %d", efd, ofd)) >= BUFSIZE)
    		die("buffer too small");
    
    	if (write(cfd, buf, wb) == -1)
    		die("write cgroup.event_control");
    
    	if (close(cfd) == -1)
    		die("close cgroup.event_control");
    
    	for (;;) {
    		if (read(efd, &u, sizeof(uint64_t)) != sizeof(uint64_t))
    			die("read eventfd");
    
    		printf("mem_cgroup oom event received\n");
    	}
    
    	return 0;
    }
    
    上記のプログラムはコマンドラインの引数として指定された cgroup 内の OOM 状態を検出し、mem_cgroup oom event received の文字列を使用して標準出力にレポートします。
  9. blue cgroup の制御ファイルを引数として指定して、上記の通知ハンドラープログラムを別のコンソールで実行します。
    ~]$ ./oom_notification /cgroup/memory/blue/cgroup.event_control /cgroup/memory/blue/memory.oom_control
  10. 別のコンソールで mem_hog テストプログラムを実行し、OOM 状態を発生させて oom_notification プログラムがそれを標準出力にレポートするのを確認します。
    blue]# ~/mem-hog



[5] ソースコード提供: Red Hat のエンジニア František Hrbata 氏
[6] ソースコード提供: Red Hat のエンジニア František Hrbata 氏