読者です 読者をやめる 読者になる 読者になる

Linuxにおける各種メモリについて

Linuxで扱う様々な種類や単位のメモリについてまとめておく。また、メモリというリソースに関する制御や管理について考察する。

ulimit

$ ulimit -a | egrep "memory|stack"
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
stack size              (kbytes, -s) 8192
virtual memory          (kbytes, -v) unlimited
  • max locked memory - mlockシステムコールを使うことにより、メモリの内容がディスク上にスワップされないようにする(=Lockする)ことができる。その最大容量。
  • max memory size - 静的(スタック領域)や動的(ヒープ領域)に確保するメモリの最大容量。
  • virtual memory - スワップされたプロセスが使用するディスク上の仮想メモリ空間の最大容量。
  • stack size - 静的(スタック領域)に確保するメモリの最大容量。

ulimitの機能

  • シェル組み込みの機能で、ユーザ毎にCPUやメモリ、ファイル、PIPEなどのリソースの使用量に制限を設ける。
  • ソフトリミットはハードリミットを超えない範囲で設定が可能で、ハードリミットを上げることができるのはrootのみ。
  • ulimitの制御は、setrlimit/getrlimitシステムコールを使って行われる。
$ strace bash -c "ulimit -S -s 65535" 2>&1 | grep -i rlimit
getrlimit(RLIMIT_NPROC, {rlim_cur=1024, rlim_max=3897}) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
setrlimit(RLIMIT_STACK, {rlim_cur=65535*1024, rlim_max=RLIM64_INFINITY}) = 0
$ strace bash -c "ulimit -H -s 819200" 2>&1 | grep -i rlimit
getrlimit(RLIMIT_NPROC, {rlim_cur=1024, rlim_max=3897}) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
setrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=819200*1024}) = 0 
  • なお、ディスク(パーティション)の使用量制限に関してはulimitではなくquotaで行う。

quota

  • ユーザ単位・グループ単位でディスク(パーティション)の使用量に制限を設ける。
  • mountオプションとしてusrquota・grpquotaを指定する必要がある。
  • ソフトリミットは猶予期間内であれば超えることができるが、猶予期間を超えるとソフトリミットがハードリミットとなる。
  • ハードリミットは超えることができない。
  • quotaの管理は、パーティションのトップディレクトリに置かれるaquota.user, aquota.groupファイル上で行われる。

ps / pmap / time*1

$ ps aux | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
$ pmap -X $(pidof bash) | head -n2
2571:   pmap -X 2571 2073
         Address Perm   Offset Device  Inode   Size  Rss Pss Referenced Anonymous Swap Locked Mapping
$ /usr/bin/time -apv ls 2>&1 | grep resident
    Maximum resident set size (kbytes): 2428
    Average resident set size (kbytes): 0
  • VSS/VSZ (Virtual Set Size) - プロセスの仮想メモリ全体のサイズ。実際には使用していない領域を含んでいる。
  • RSS (Resident Set Size) - プロセスが仮想メモリの中で、実際に使用しているメモリのサイズ。共有ライブラリのメモリを含むため、各プロセスで重複してカウントされている。また、スワップアウトされているメモリは含まれない。
  • PSS (Proportional Set Size) - プロセスが実際に使用しているメモリのサイズ。共有ライブラリのメモリを含むが、そのライブラリを使用しているプロセス数で割っているため、RSSの問題点は解決している。

  • (例)dhclientとcupsdはlibcryptoを共有ライブラリとして利用しており、それぞれのプロセスのRSSとしてカウントされている。

$ ldd /sbin/dhclient | grep libcrypto
    libcrypto.so.10 => /usr/lib64/libcrypto.so.10 (0x00007f62d75f7000)
$ ldd /usr/sbin/cupsd | grep libcrypto
    libcrypto.so.10 => /usr/lib64/libcrypto.so.10 (0x00007fd442cef000)
$ ps -FC dhclient
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root      2394   842  0 25553 19040   0 23:27 ?        00:00:00 /sbin/dhclient -d -sf /usr/libexec/nm-dhcp-helper -pf /var/run/dhclient-p2p1.pid -lf /var/lib/
$ ps -FC cupsd
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root      1709     1  0 47262  2044   0 23:11 ?        00:00:00 /usr/sbin/cupsd -f
$ man ps
...
       rss         RSS       resident set size, the non-swapped physical
                             memory that a task has used (inkiloBytes).
                             (alias rssize, rsz).
...
  • pmapのRSSとSizeは、実際にメモリに読み込まれているかどうかの違いがある。この違いは、デマンドページングによりページフォルトが発生するまで実際のメモリへの読み込みを遅延する、ことによって現れる。それによって、例えば共有ライブラリの中の実際に使用する関数のみをメモリに読み込んで実行することができる。Sizeは実際には読み込まれておらず、論理アドレスに割り当てただけの部分もカウントしている。

  • デマンドページングはmalloc発行時にも効果を発揮する。mallocを発行すると論理アドレス上ではメモリを割り当てたことになるが、物理メモリ上ではまだメモリ領域は確保されず、実際にメモリに書き込んだ際、初めて物理メモリ上でメモリ領域が確保される。この機構により実際の物理メモリ容量よりも大きいメモリをmallocにより割り当てることができ、これをオーバーコミットと呼ぶ。ただし、実際に物理メモリ以上の領域に書き込もうとすると、メモリ不足の例外が発生するか、OOM Killerにより停止されるだろう。オーバーコミットの容量はsysctlである程度制御が可能である。

/proc/meminfo

$ cat /proc/meminfo | egrep -i "active|dirty|claim|mem|slab"
MemTotal:        1017448 kB
MemFree:           97304 kB
MemAvailable:     316264 kB
Active:           317736 kB
Inactive:         371132 kB
Active(anon):     209564 kB
Inactive(anon):   244268 kB
Active(file):     108172 kB
Inactive(file):   126864 kB
Dirty:               324 kB
Shmem:              3444 kB
Slab:             192720 kB
SReclaimable:     152860 kB
SUnreclaim:        39860 kB
  • anon(Anomymous) - プロセスが確保したメモリ。ディスクに退避(スワップ)されることがある。
  • file(File-backed) - OSがディスクキャッシュとして確保したメモリ。(Dirtyなもの以外は)もともとディスク上に存在するデータ。
  • Shmem - tmpfsとして使用されているメモリ。tmpfsはメモリ上にのみ存在するが、スワップの対象となりディスクに書き出される。

  • Active/Inactive - LRU的に、最近使用されたメモリか、利用されていないメモリか。

  • Dirty - ディスクに書き込まれていないFile-backedなバッファ。syncでディスクに書き込むと0になり、Cleanなバッファとなる。Cleanなバッファは、echo 1 > /proc/sys/vm/drop_cachesで解放される。

$ cat /proc/meminfo  | grep Dirty
Dirty:              1032 kB
$ sync
$ cat /proc/meminfo  | grep Dirty
Dirty:                 0 kB
  • Slab - SReclaimable + SUnreclaim

    • SReclaimable - 解放可能なSlab。 echo 2 > /proc/sys/vm/drop_cachesで解放される。
    • SUnreclaim - 解放不可能なSlab。
  • echo 2 > /proc/sys/vm/drop_caches の直後でもSReclaimableは0にならないが、slabtopで確認するとdentryやinodeがSlabを使用しているのが分かる。バックグラウンドプロセスやカーネルの処理が動いているため、すぐにSlabの割り当てが行われているのだろう。

$ cat /proc/meminfo | egrep -i "slab|claim"
Slab:             210504 kB
SReclaimable:     170240 kB
SUnreclaim:        40264 kB
$ echo 2 > /proc/sys/vm/drop_caches 
$ cat /proc/meminfo | egrep -i "slab|claim"
Slab:              79436 kB
SReclaimable:      45460 kB
SUnreclaim:        33976 kB
$ slabtop -o -sc | head -n 12
 Active / Total Objects (% used)    : 579600 / 661586 (87.6%)
 Active / Total Slabs (% used)      : 11754 / 11754 (100.0%)
 Active / Total Caches (% used)     : 74 / 100 (74.0%)
 Active / Total Size (% used)       : 51588.87K / 78695.04K (65.6%)
 Minimum / Average / Maximum Object : 0.01K / 0.12K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
 29368   8839  30%    0.99K   1894       16     30304K ext4_inode_cache       
  9674   9378  96%    0.55K    691       14      5528K inode_cache            
 63189  20416  32%    0.08K   1239       51      4956K Acpi-State             
 21210  12372  58%    0.19K   1010       21      4040K dentry                 
118528 118528 100%    0.03K    926      128      3704K kmalloc-32   
$ slabtop --help
...
The following are valid sort criteria:
 c: sort by cache size
...
  • VmallocUsed - カーネルが使用しているSlab以外のメモリ。ただし、デバイス上の物理メモリを含んでいる。デバイス上の物理メモリは/proc/vmallocinfoioremapの部分。
$ sudo cat /proc/vmallocinfo | grep ioremap
0xffffc90000000000-0xffffc90000004000   16384 acpi_os_map_iomem+0xf7/0x150 phys=3fff0000 ioremap
0xffffc900001f8000-0xffffc900001fb000   12288 pci_iomap+0x55/0xb0 phys=f0806000 ioremap
0xffffc900001fc000-0xffffc900001fe000    8192 usb_hcd_pci_probe+0x14d/0x550 phys=f0804000 ioremap
0xffffc90000280000-0xffffc900002a1000  135168 pci_ioremap_bar+0x46/0x80 phys=f0000000 ioremap
0xffffc90000300000-0xffffc90000701000 4198400 vgdrvLinuxProbePci+0x9c/0x210 [vboxguest] phys=f0400000 ioremap

$ sudo cat /proc/vmallocinfo | grep ioremap | awk '{print $2}' | paste -s -d '+'
16384+12288+8192+135168+4198400
$ sudo cat /proc/vmallocinfo | grep ioremap | awk '{print $2}' | paste -s -d '+' | bc
4370432
  • PageTables - 各プロセスにおける論理アドレスから物理アドレスへの変換表。なお、現在CPUで実行中のプロセスのページテーブルは、CR3レジスタでそのメモリアドレスが示される。

free

  • /proc/meminfoの内容を元に、メモリの使用量や残量を示してくれる。なおx86(32bit)では、lオプションはカーネルが使用するLowメモリと、プロセスが使用するHighメモリを分けて表示してくれる。なおカーネルが使用するLowメモリは、物理アドレスの1MB〜893MBまで、また各プロセスの論理アドレスの3GB〜4GBの領域を占める。x86_64(64bit)では、LowメモリやHighメモリといった区別が無いため、lオプションで表示する場合は全てLowメモリとして表示し、Highメモリは常に0として表示されるようである。
$ free -l
             total       used       free     shared    buffers     cached
Mem:       1017448     949096      68352       4004      88984     131476
Low:       1017448     949096      68352
High:            0          0          0
-/+ buffers/cache:     728636     288812
Swap:       839676      45872     793804

Slab

  • メモリの割り当てや解放を繰り返して毎回初期化するよりも、カーネルはOSにメモリを戻さずに(解放せずに)初期化した状態のまま保持し続けておき、 必要になったらそこから再利用することでパーフォーマンスが上がる。Slabはオブジェクトキャッシュとも呼ばれる。

  • 小さい単位のメモリ割り当て・解放を、その都度メモリから自由に行うとフラグメントの問題を引き起こしやすくなるが、Slabとして保持しておいて再利用することで、少なくともスラブを利用している限りにおいてはフラグメントの問題は起こらない。

  • SlabのAPI

    • kmem_cache_create - Slabを確保する
    • kmem_cache_alloc - Slabから実際に使うオブジェクトへメモリを払い出す
    • kmem_cache_free
  • Slabは単一のオブジェクトを連続したメモリ上に複数格納する。

http://4.bp.blogspot.com/-zfUCD_rkxMI/VKkcpQ789dI/AAAAAAAABrQ/j1fpLSFTrVU/s1600/study%2B%283%29.png

  • Slabがemptyになると、リープ(OSに戻す)対象となる。

http://1.bp.blogspot.com/-G4jHb5oQfyM/VKkcpZo14fI/AAAAAAAABrM/y-FCpnX7cKQ/s1600/slab%2B%281%29.png

  • Cache Coloring - Slabのオブジェクトはページに沿って配置されているため、CPUの特定のキャシュラインに集中してしまう。別のオブジェクトがCPUの別のキャッシュラインに載るように、Slabごとに少しフラグメントを持たせてアドレスをずらす。

http://www.secretmango.com/jimb/Whitepapers/slabs/slabcolor.gif

Slabが大量消費されていることでメモリを圧迫する問題(問題?)

  • dentryやinodeはSlabのヘビーユーザであり、それによってSlabが大量に消費されていることがある。/proc/sys/vm/drop_cachesに2 or 3を書き込むことで、Slab(SReclaimable)は解放されるが、Slabを完全に解放してしまうとLinuxとしての性能は格段に落ちる。

    • dentry - Linuxには、システムコールから各種ファイルシステムを抽象化して統一的に扱うために、VFSの層が存在する。VFSでは、dentryという構造体を使用してディレクトリ構造やファイル名とinodeとの対応などをキャッシュする。なお、dentryはメモリ上にのみ存在し、ファイルシステムの一部のデータとして存在しているわけではない。
    • inode - ファイルシステムでファイルを管理するためのメタ情報。ファイルのサイズやパーミッション、データブロックの位置などが管理されている。
    • ページキャッシュ - ファイルのデータブロックのキャッシュ。
  • そもそもメモリをたくさん消費しているのはリソースを効率良く使用できている状態とも言えるので、問題なのはそれによってスワッピングが発生しているか否かであり、それはvmstatコマンドのsi,soの項目を確認することで分かる。SlabもLRUによって管理されているはずなので、基本的にはそれにまかせるのが得策なのではないだろうか。 なお、vmstatで最初に表示される結果は起動時からの平均値であり、単位時間の結果を確認する場合には二つ目以降を確認する。

$ vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 1  1  51596  75360  71636 172804    1   10   337    19  142  462  1  2 96  1

$ man vmstat
...
   Swap
       si: Amount of memory swapped in from disk (/s).
       so: Amount of memory swapped to disk (/s).
...
  • Slabのページキャッシュに対する比率を制御するには、/proc/sys/vm/vfs_cache_pressure を使う。100を基準として、Slabの比率を増やしたり減らしたりできるらしい。その他、/proc/sys/vmで管理されているパラメータについてはこちら → https://www.kernel.org/doc/Documentation/sysctl/vm.txt

Slabの肥大化で実際にスワップが発生した問題とその解決方法

  • いきなり上記で書いた内容と矛盾するが、Slabの肥大化によってスワップが発生してしまった事例がある。つまり、メモリ圧迫時にはLRUに基づくSlabの解放を期待したがうまく動いてくれなかった、ということになる。その原因として考えられるのは、dentry cacheを解放するスピードが間に合わないのか、とある。解決方法としては、tmpfsを使うことによりSlabの肥大化を回避できる、ということで、その理由としてはtmpfsがLinuxのVFSを使わないからでしょうかとのことだが、tmpfsではそもそもdentry cacheを生成しないのだろうか。この事例から「一時ファイルはtmpfsにおいて処理する」は鉄則だろう。

blog.nomadscafe.jp

  • negative dentry - 存在しないパスを開くと、それについてもnegativeなdentry cacheとしてキャッシュする。dentry cacheにヒットしない場合、ファイルシステムに問い合わせることでディスクI/Oが発生するが、negative entryがあればその必要が無いのでディスクI/Oを減らす効果が期待できる。negative dentryはsar -vコマンドの結果のdentunusdが示す値に含まれているらしい。なお、negative cacheはDNSでも不要なクエリを発生させないために使われることがある。
$ sar -v | tail -n +3 | head -n4
12:10:01 AM dentunusd   file-nr  inode-nr    pty-nr
12:20:01 AM     80492      4832     75177         1
12:30:01 AM     80506      4832     75184         1
12:40:01 AM     80495      4800     75166         1
$ man sar
...
              dentunusd
                     Number of unused cache entries in the directory cache.
...

d.hatena.ne.jp

ページキャッシュを効率よく管理するためのアプローチ

  • cachectld - posix_fadviseとPOSIX_FADV_DONTNEEDをディレクトリ単位で適用することにより、必要なページキャッシュは残しつつ、不要なページキャッシュだけをディレクトリ単位で解放する。

tech.mercari.com

ページキャッシュが不要ならO_DIRECTを使うアプローチは?

  • openシステムコールにO_DIRECTフラグを渡すと、ファイルへの書き込みや読み込みをVFSを介さずにファイルシステムと直接行うため、メモリにページキャッシュを残すことが無い。これは、再利用することが無いと分かっている大きなファイルを開くときには、無用にメモリを使用することが無く、他のAnonymousやFile-backedなメモリに対して優しいと言える。ただし書き込みの際は、バッファへの書き込みが行われず、プロセスがディスクへの書き込み待ちで長い時間ブロックされることになるため注意が必要。

  • 書き込みはページキャッシュとしてメモリに蓄えられた後で非同期に(flusherスレッドによってデフォルト5秒間隔で)ディスクへ書き込まれるので、通常はプロセスが書き込みでブロックされることはない。一時的にメモリを消費してしまうものの、ディスクへ書き込んだ直後に該当のページキャッシュを解放するような仕組みがあっても良さそう。

ramfs

  • ramfsはtmpfsのようにメモリ上に作成されるファイルシステムだが、ramfsの方は物理ディスクがないにも関わらず、File-backedとして登録されるため、ディスクにスワップされることがない。

プロのための Linuxシステム構築・運用技術 (Software Design plus)

プロのための Linuxシステム構築・運用技術 (Software Design plus)

*1:シェル組み込みのtimeではなく、シェルスクリプトとしてインストールされている/usr/bin/timeの方