Hatena::ブログ(Diary)

ablog このページをアンテナに追加 RSSフィード Twitter

2015-09-03

Linux のページテーブルのサイズの見方と見積式

Linux Kernel 2.6 (x86-64) でのページテーブルのサイズの確認方法と見積式を調べてみた。

あっているか自信のないところもある&まだ書きかけ。


ページテーブルのサイズの見方

  • OS全体のページテーブルのサイズ
$ cat /proc/meminfo 
MemTotal:       16158544 kB
MemFree:        13134056 kB
(中略)
PageTables:        34428 kB ★ 34MB
$ cat /proc/10225/status # 10255 は PID
Name:	zsh
State:	S (sleeping)
Tgid:	10225
Pid:	10225
PPid:	10222
(中略)
VmPTE:	     124 kB ★ 124KB

ページテーブルのサイズの見積

見積
( プロセスが参照するメモリサイズ / 4KB(ページサイズ) ) * 12 bit(PTEの1エントリのサイズ) 
1byte = 8bit

Oracle Database でSGA(共有メモリ)が 1GB の場合、1プロセスが共有メモリに使うページテーブルのサイズは以下の通り。

( 1,073,741,824 / 4,096 ) * 12bit = 3,145,728 bit / 8 = 393,216 byte = 393 KB
絵で見てわかる見積式の仕組み

f:id:yohei-a:20150903091950p:image:w640




補足説明

ページテーブルとは
  • メインフレームの時代からOSは仮想記憶という仕組みで、物理メモリ以上のサイズ(物理メモリ + スワップ領域)をメモリ領域として使えるようになっています。
    • Linuxデフォルトで、物理メモリ + スワップ領域を超えるサイズを仮想的に割当てができたはず(オーバーコミット)
  • 仮想記憶には仮想アドレス空間をページング方式(固定長で分割)とセグメント方式(可変長で分割)があり、ほとんどのOSはページング方式を採用していると思います。
  • ページング方式では、ページテーブルと呼ばれるデータ構造にアドレス変換テーブル(仮想ページ番号と物理ページ番号のマッピング情報)が格納されます。
  • ページテーブルはユーザ空間ではなくカーネル空間の領域です。ps や pmap などで見れるプロセスがユーザー空間で使用するメモリ領域には含まれません。
  • ページング方式などの仮想記憶はメモリ管理ユニット(MMU)と呼ばれるハードウェアで実現され、OSはその仕様に準じた実装をしています。

Linux のページテーブル
  • Linux で共有メモリを複数のプロセスから使うと、同じ共有メモリのアドレス空間について、各プロセスで個別にページテーブルを持つため、共有メモリサイズが大きく、共有メモリを使うプロセスが多いと、ページテーブルとして使うサイズが大きくなります。
  • Solaris は ISM や DISM などにより、共有メモリのページテーブルをプロセス間で共有されるため、Linuxのようにページテーブルのサイズが大きくなることはありません。

Oracle Database on Linux でのページテーブル
  • Oracle Database はマルチプロセスで共有メモリを使うため、SGA(共有メモリ)が大きく、セッション数が多いと、塵も積もれば山となるで、ページテーブルのサイズが大きくなります。
  • OS内の管理領域であるページテーブルに何〜何十GBのページテーブルを使うのはもったいないので、SGAが大きくセッション数がか多い場合は HugePages を使うとページテーブルのサイズが小さくなり、メモリを節約できます。
  • 通常のページは4KBですが、HugePagesでは2MBになります。512倍になるため、ページを管理するPTE の数が少なくなり、ページテーブルのサイズも小さくなります。

検証結果(追記予定)

SGA が1GB のインスタンスに100セッションの接続を張ると、1セッションで400KB弱、100セッションで 40MB 程度をページテーブルとして使いましたという検証結果を記載予定

  • 100セッション接続前のメモリ使用量とページテーブルのサイズ
  • 100セッション接続する
  • ページテーブルのサイズが大きくなる
  • 1プロセス当りのベージテーブルのサイズは●●KB程度
  • 100セッション切断するとベージテーブルは解放される

Linux Kernelソースコードより

id:naoya さんのブログエントリでわかりやすく解説されているので、そのまま引用します。

/proc/<PID>/status の出力の詳細を知る

/proc/<PID>/status はプロセスのメモリ利用状況を詳細に出力するので、重宝します。各行の意味するところを正確に把握しておきたいところです。Linux カーネルソースの Documentation/filesystems/proc.txt に一応ドキュメントがありますが、残念ながら詳細な言及はありません。

そこで、ソースを見ます。少し古いですが、linux-2.6.23 のソースを見ていきます。/proc/<PID>/status を read すると、fs/proc/array.c にある 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/<PID>/status で指定した PID のプロセスプロセスディスクリプタ (task_struct 構造体)で、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 の各行の意味は明確になるでしょう。

あるプロセスが利用しているメモリサイズを procfs 経由で調べる - naoyaのはてなダイアリー

VmPTE は以下の式で計算されていることがわかります。

  • fs/proc/task_mmu.c
(PTRS_PER_PTE*sizeof(pte_t)*mm->nr_ptes) >> 10); 
512個 * 12bit(PTEの1エントリのサイズ) * ページエントリのセットの数 
  • arch/x86/include/asm/pgtable_64_types.h
/*
 * entries per page directory level
 */
#define PTRS_PER_PTE    512

前提


参考


To Do

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/yohei-a/20150903/1441252765
リンク元
おとなり日記