あるプロセスが利用しているメモリサイズを procfs 経由で調べる
お題は「あるプロセスがどの程度の物理メモリを利用したかを知りたい」です。
手っとりばやく知りたいときは top や ps などで調べると良いでしょうか。例えば手元の coLinux で top して M キーでソートすると emacs のプロセスが最もメモリを使っているようです。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1923 naoya 18 0 23120 19m 3096 S 0.0 2.0 0:55.40 emacs
メモリサイズは VIRT と RES がありますが、VIRT は Virtual の略で仮想メモリ領域のサイズ、RES が Resident の略で、実際に使用している物理メモリ領域のサイズ。19MB ほど使っているようです。この emacs のプロセスが利用するメモリ領域はざっくり 20MB 程度と考えることはできます。
しかし、ちょっと雑なのでもう少し詳しく見たい。こういう時は /proc/
% cat /proc/1923/status Name: emacs State: S (sleeping) SleepAVG: 77% Tgid: 1923 Pid: 1923 PPid: 1545 TracerPid: 0 Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000 FDSize: 32 Groups: 108 1000 VmPeak: 23124 kB VmSize: 23120 kB VmLck: 0 kB VmHWM: 19968 kB VmRSS: 19964 kB VmData: 13800 kB VmStk: 352 kB VmExe: 1024 kB VmLib: 4064 kB VmPTE: 32 kB Threads: 1 ...以下略
Vm* の行がメモリに関するステータスです。VmSize が先ほどの top での VIRT 列、VmRSS が RES 列に相当します。他の行は何でしょうか。結論からいくと
行名 | 説明 | |
---|---|---|
VmPeak | このプロセスがある時点で使っていた最大仮想メモリサイズ | |
VmLck | ロックされている(スワップアウトされない)メモリのサイズ (mlock(2) などでロックできます) |
|
VmHWM | このプロセスがある時点で使っていた最大物理メモリサイズ | |
VmData | このプロセスの動的仮想メモリ領域のサイズ | |
VmStk | スタックサイズ | |
VmExe | 実行ファイル (テキスト領域) のサイズ | |
VmLib | ロードされたライブラリのサイズ | |
VmPTE | ページテーブルのサイズ |
になります。*1 VmHWM あたりが今回の場合特に気になる指標でしょう。
この emacs プロセスは VmHWM が 19968 kB なので、約 19 MB。VmRSS も 19964 kB で 19 MBほどでほとんど差はありません。ピーク時でも 19MB 程度なのが分かりました。
一方、これとは別のとあるデーモンプロセスの status を見てみます。このプロセスは大量のデータを用いた計算を行うデーモンプロセスで、動的に巨大なメモリの割り当てと開放を繰り返します。Vm* 行の出力は以下のようになりました。
VmPeak: 1323048 kB VmSize: 884116 kB VmLck: 0 kB VmHWM: 861796 kB VmRSS: 498316 kB VmData: 858276 kB VmStk: 88 kB VmExe: 124 kB VmLib: 5032 kB VmPTE: 1276 kB
と VmRSS は 500MB 弱なのに対して VmHWM が 860 MB ほどあります。この場合 top や ps の値で一時的に 500MB 程度の値を見て「500 MB くらい」と判断するのは早計ですね。ピーク時に 860 MB まで行っているので、その分を加味しておく必要があります。
また、VmRSS にはプロセスが他プロセスと共有しているメモリ領域のサイズも含まれるので、複数プロセスに渡って利用している実メモリサイズを調べる場合はその点も考慮する必要があるでしょう。詳しくは Linux のプロセスが Copy on Write で共有しているメモリのサイズを調べる - naoyaのはてなダイアリー に記しています。
/proc//status の出力の詳細を知る
/proc/
そこで、ソースを見ます。少し古いですが、linux-2.6.23 のソースを見ていきます。/proc/proc_pid_status()
関数が呼ばれます。
int proc_pid_status(struct task_struct *task, char * buffer) { char * orig = buffer; struct mm_struct *mm = get_task_mm(task); buffer = task_name(task, buffer); buffer = task_state(task, buffer); if (mm) { buffer = task_mem(mm, buffer); mmput(mm); } buffer = task_sig(task, buffer); buffer = task_cap(task, buffer); buffer = cpuset_task_status_allowed(task, buffer); #if defined(CONFIG_S390) buffer = task_show_regs(task, buffer); #endif return buffer - orig; }
引数の task
は /proc/task->mm
でメモリディスクリプタ (mm_struct 構造体) が得られます。status の出力で表示されているメモリ関連の行の値はメモリディスクリプタに収められています。
proc_pid_status()
では get_task_mm(task)
でメモリディスクリプタを取得し、task_mm(mm, buffer)
でメモリディスクリプタ内から必要な値を取得し、出力を作っています。task_mm()
は以下のような実装になっていました。
char *task_mem(struct mm_struct *mm, char *buffer) { unsigned long data, text, lib; unsigned long hiwater_vm, total_vm, hiwater_rss, total_rss; /* * Note: to minimize their overhead, mm maintains hiwater_vm and * hiwater_rss only when about to *lower* total_vm or rss. Any * collector of these hiwater stats must therefore get total_vm * and rss too, which will usually be the higher. Barriers? not * worth the effort, such snapshots can always be inconsistent. */ hiwater_vm = total_vm = mm->total_vm; if (hiwater_vm < mm->hiwater_vm) hiwater_vm = mm->hiwater_vm; hiwater_rss = total_rss = get_mm_rss(mm); if (hiwater_rss < mm->hiwater_rss) hiwater_rss = mm->hiwater_rss; data = mm->total_vm - mm->shared_vm - mm->stack_vm; text = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK)) >> 10; lib = (mm->exec_vm << (PAGE_SHIFT-10)) - text; buffer += sprintf(buffer, "VmPeak:\t%8lu kB\n" "VmSize:\t%8lu kB\n" "VmLck:\t%8lu kB\n" "VmHWM:\t%8lu kB\n" "VmRSS:\t%8lu kB\n" "VmData:\t%8lu kB\n" "VmStk:\t%8lu kB\n" "VmExe:\t%8lu kB\n" "VmLib:\t%8lu kB\n" "VmPTE:\t%8lu kB\n", hiwater_vm << (PAGE_SHIFT-10), (total_vm - mm->reserved_vm) << (PAGE_SHIFT-10), mm->locked_vm << (PAGE_SHIFT-10), hiwater_rss << (PAGE_SHIFT-10), total_rss << (PAGE_SHIFT-10), data << (PAGE_SHIFT-10), mm->stack_vm << (PAGE_SHIFT-10), text, lib, (PTRS_PER_PTE*sizeof(pte_t)*mm->nr_ptes) >> 10); return buffer; }
この実装を見ることで、status の各行の意味は明確になるでしょう。
VmPeak は mm->hiwater_vm
か mm->total_vm
で高い値を示したもの。詳解 Linuxカーネル 第3版 によると、mm->hiwater_vm
は「このプロセスのメモリリージョンがある時点で使っていたページフレームの最大数」です。mm->total_vm
は「プロセスアドレス空間全体の大きさをページ数として表す」メンバです。
VmHWM は mm->hiwater_rss
か mm->rss
のどちらかで高い値を示した物。mm->hiwater_rss
は「このプロセスがある時点で使っていたページフレームの最大数」で、mm->rss
は「プロセスに割り当てられているページフレームの数」です。ページフレームのサイズは固定なので、ページフレーム数 → バイト数に変換するとメモリサイズになります。
なお、get_mm_rss(mm)
は mm から mm->file_rss
と mm->anon_rss
メンバの値を取得するマクロ、get_mm_counter()
はメモリディスクリプタから任意のメンバの値を取得するマクロで以下のような実装になっています。CPU の数によっては操作をアトミックにするという抽象化が行われています。
#if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS #define get_mm_counter(mm, member) ((unsigned long)atomic_long_read(&(mm)->_##member)) #else #define get_mm_counter(mm, member) ((mm)->_##member) #endif #define get_mm_rss(mm) \ (get_mm_counter(mm, file_rss) + get_mm_counter(mm, anon_rss))
他の行も同様にメモリディスクリプタの各メンバの値から計算されています。説明は省略します。
まとめ
参考文献
- 作者: Daniel P. Bovet,Marco Cesati,高橋浩和,杉田由美子,清水正明,高杉昌督,平松雅巳,安井隆宏
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/02/26
- メディア: 大型本
- 購入: 9人 クリック: 269回
- この商品を含むブログ (73件) を見る
*1:メモリサイズと記載していますが、実際にはページ数が基礎になっていて、表示の際にページ数がメモリ領域のサイズに変換されています。