Linuxと物理メモリについて

Linux
スポンサーリンク

はじめに

ラズパイなど組み込みLinuxを使用しているとメモリ関係の問題が発生することがあります。今回、物理メモリについて、勉強したことをまとめていきたいと思います。

勉強中のため誤った情報を載せてしまう場合がございます。もし何かおかしいところありましたら、コメントかTwitterで優しく教えて頂けると幸いです。あとで、こっそり直します(笑)

物理メモリとページ

buddyinfoとページ割り当て数

単純にメモリを使用するとは言っても、物理メモリと仮想メモリを分けて考えなければなりません。物理メモリは、4KBのページサイズと呼ばれる最小の区切りを使用して、仮想メモリに割り当てられます。

buddyinfo

物理メモリを使っていることを実感するためには、buddyinfo情報を見るとよいでしょう。

[pi@centos-rpi2 ~]$ cat /proc/buddyinfo
Node 0, zone   Normal    306    367    531     40     17      4      3      2      1      0      0

この情報は、利用できる物理メモリも示しています。

左端で306という数字があるかと思いますが、これは4KBのページが306個あるということになります。続いて右の367は、8KBのページが367個あるということになります。これが右に、4KB、8KB、16KB……と続いていきます。

なお、buddyinfoは、連続した物理メモリ領域がどれだけあるかという観点で残り容量が分かります。例えば上の例では、64KBの連続した物理メモリ領域は17個あるということが分かります。

LowMemoryとHighMemory

buddyinfoを表示した際にシステムによっては、HighMemが表示される場合があります。

Node 0, zone      DMA     90      6      2      1      1      ...
Node 0, zone   Normal   1650    310      5      0      0      ...
Node 0, zone  HighMem      2      0      0      1      1      ...

Red Hat Training – E.2. PROC ファイルシステム内の最上位のファイル より

実は物理メモリは、LowMemoryとHighMemoryに分かれており、NormalがLowMemoryに相当します。

このうちLinuxのシステムが使用しているカーネル空間で確保できる物理メモリは、基本的にLowMemoryからしか確保ができません※。HighMemoryは、プロセスごとが持つユーザー空間からでしか利用できないのです。従って、HighMemoryが残ってたとしても、LowMemoryが少なければシステムが不安定な状況になります。

※正確に言うと、カーネルで利用できるkmallocGFP_HIGHUSERを指定すれば利用が可能ですが、細かいことは不明です。

参考

topとfreeとbuddyinfoとmeminfoから分かる空き物理メモリ

情報の読み方について

メモリの情報を確認する場合に、topfreebuddyinfomeminfoといった情報があるのですが、これらは一体どのような情報がとれるのでしょうか。

ここでは物理メモリに着目してとれる情報を確認していきます。なお、取れた情報の中にスワップサイズも含まれますが、これは後程覚えていたら解説します。

実際に実行してみる

top

[pi@centos-rpi2 ~]$ top

top - 23:35:59 up 203 days, 23:53,  1 user,  load average: 0.00, 0.00, 0.00
Tasks:  93 total,   1 running,  55 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.4 us,  4.8 sy,  0.0 ni, 92.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :   949424 total,    17236 free,    33268 used,   898920 buff/cache
KiB Swap:   524284 total,   477948 free,    46336 used.   834480 avail Mem

free -t

[pi@centos-rpi2 ~]$ free -t
              total        used        free      shared  buff/cache   available
Mem:         949424       33360       17112       33056      898952      834388
Swap:        524284       46336      477948
Total:      1473708       79696      495060

cat /proc/buddyinfo

[pi@centos-rpi2 ~]$ cat /proc/buddyinfo
Node 0, zone   Normal     34    375    530     41     17      4      3      2      1      0      0

cat /proc/meminfo

[pi@centos-rpi2 ~]$ cat /proc/meminfo
MemTotal:         949424 kB
MemFree:           17312 kB
MemAvailable:     834580 kB
Buffers:           25484 kB
Cached:            68956 kB
SwapCached:         2236 kB
Active:            72548 kB
Inactive:          42840 kB
Active(anon):      27108 kB
Inactive(anon):    26896 kB
Active(file):      45440 kB
Inactive(file):    15944 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        524284 kB
SwapFree:         477948 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         19104 kB
Mapped:            23540 kB
Shmem:             33056 kB
Slab:             804504 kB
SReclaimable:     771384 kB
SUnreclaim:        33120 kB
KernelStack:         944 kB
PageTables:         1740 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      998996 kB
Committed_AS:     306884 kB
VmallocTotal:    1114112 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
CmaTotal:           8192 kB
CmaFree:               0 kB

表にまとめてみる

情報をまとめます。

top

名前 サイズ
total 949424 KiB
free 17236 KiB
used 33268 KiB
buff/cache 898920 KiB

free

名前 サイズ
total 949424 KiB
used 33360 KiB
free 17112 KiB
shared 33056 KiB
buff/cache 898952 KiB
available 834388 KiB

buddyinfo

ページサイズ 個数 合計
4 KiB 34 136 KiB
8 KiB 375 3000 KiB
16 KiB 530 8480 KiB
32 KiB 41 1312 KiB
64 KiB 17 1088 KiB
128 KiB 4 512 KiB
256 KiB 3 768 KiB
512 KiB 2 1024 KiB
1024 KiB 1 1024 KiB
17344 KiB

meminfo

名前 サイズ
MemTotal 949424 KiB
MemFree 17312 KiB
MemAvailable 834580 KiB
Buffers 25484 KiB
Cached 68956 KiB
SwapCached 2236 KiB
Active 72548 KiB
Inactive 42840 KiB
Active(anon) 27108 KiB
Inactive(anon) 26896 KiB
Active(file) 45440 KiB
Inactive(file) 15944 KiB
Unevictable 0 KiB
Mlocked 0 KiB
SwapTotal 524284 KiB
SwapFree 477948 KiB
Dirty 0 KiB
Writeback 0 KiB
AnonPages 19104 KiB
Mapped 23540 KiB
Shmem 33056 KiB
Slab 804504 KiB
SReclaimable 771384 KiB
SUnreclaim 33120 KiB
KernelStack 944 KiB
PageTables 1740 KiB
NFS_Unstable 0 KiB
Bounce 0 KiB
WritebackTmp 0 KiB
CommitLimit 998996 KiB
Committed_AS 306884 KiB
VmallocTotal 1114112 KiB
VmallocUsed 0 KiB
VmallocChunk 0 KiB
CmaTotal 8192 KiB
CmaFree 0 KiB

要点

情報が多いので、私なりに物理メモリを調査する場合に重要な箇所をまとめてみました。

英語 表示方法 意味
MemTotal, total top, free, meminfo 物理メモリ量
MemFree, free top, free, buffyinfo, meminfo 空き物理メモリ
Inactive(anon) meminfo スワップによって開放できる可能性がある使用中の物理メモリ
Inactive(file) meminfo 開放できる可能性がある使用中の物理メモリ

topコマンドでもある程度わかりますが、メモリ関係を見るならmeminfoの情報が多く大事であることが分かりました。

上記でまとめた解説から、すぐに利用できる物理メモリは、以下のようになります。

MemFree ≦ 空きメモリ ≦ MemFree + Inactive(anon) + Inactive(file)

スワッピング※を使用していない場合は、以下のようになります。

MemFree ≦ 空きメモリ ≦ MemFree + Inactive(file)

※スワッピングについては後程、解説します。

参考

キャッシュの開放タイミング

OSは適時に開放処理を行います。

min_free_kbytesの設定

開放処理は、min_free_kbytesの設定が関係します。この情報は、/proc/sys/vm/min_free_kbytesを見るか、カーネルパラメータ操作用のsysctlvm.min_free_kbytesを見ることで確認が可能です。

pi@raspberrypi:~ $ cat /proc/sys/vm/min_free_kbytes
16384
pi@raspberrypi:~ $ sysctl -n vm.min_free_kbytes
16384

一時的に変更する場合は、以下のように実行することで変更できます。

echo xxx > /proc/sys/vm/min_free_kbytes
or
sysctl -w vm.min_free_kbytes=xxx

恒久的に変更する場合は、/etc/sysctl.confを編集すればよいのですが、カーネルコンフィグで有効にしない場合にファイルが存在しない場合があります。起動中でも変更が可能なので、OSの起動時に毎回実行するようにしておきましょう。

min_free_kbytesと空きメモリの関係性

min_free_kbytesの値が大きいほど、空き物理メモリ(MemFree)を大きくとるように、早めにキャッシュを開放しようとします。ただ、具体的にどの値から解放していくか調査しましたが、いまいち不明です。実機で一度具体的な数値を入れて調査したほうが良いと思われます。

確実に言えることは、この値が小さいほど、空き物理メモリがギリギリの状況でOSを動かすことになるため、メモリ開放しようとしたタイミングで物理メモリが足らないといった状況が起きやすくなります。この値が大きいほど、早めにキャッシュを開放してしまうため、物理メモリに余裕があるにもかかわらずスワッピングを発生させて処理が重たくなる場合があります。

参考

物理メモリの開放手段

これまでスワッピングがどうこう解説してきましたが、動作の解説を説明していませんでしたので解説します。なおメモリ開放に関する手法はスワッピング以外にも方法があるため、合わせて紹介します。

私の中ではメモリの開放する手段は大きく分けて3つあると認識しています。もしこれ以外に、大きな考慮するべき手段がありましたらTwitterとかで教えてください。

  • スワッピング
  • ページの解放
  • メモリコンパクション

以下、解説していきます。

スワッピング

物理メモリが足らなくなった場合に、ハードディスクなどのドライブに一時的に物理メモリ上のデータを移動させて、物理メモリに空きを作る技術です。meminfo情報にあるInactive(anon)という部分が、スワッピングすることで物理メモリの空きを作れる可能性を持つ領域となります。

ハードディスクは速度が遅いため、ここにデータが移動されるとシステムの動作が遅くなります。ただ、止まりはしないですし、物理メモリ不足でシステムが落ちてしまわないように最後の砦という意味で設定しておくと安心かと思われます。よくスワッピングを止めるような記事がネット上で見つかりますが、サーバー用途であるならば私として設定を有効化したほうがいいと思っています。

参考

ページの開放

空のページをより大きなブロックに連結するという処理になります。

もうすこし分かりやすく解説するために、buddyinfoを例に解説します。buddyinfoはある大きさのページサイズの物理メモリの数を表しています。例えばカーネルが非常に大きな連続した物理メモリを使用したい場合に、小さい物理メモリは大量にあるのに大きい物理メモリがないと、カーネルは”page allocation failure”を発生させてしまいます。このような時のために、ページ開放を行うことで小さい物理メモリを結合して大きい物理メモリを作成し、メモリを確保できるようにするのです。

しかし、ページの開放を何度も続けていると、小さな物理メモリ領域が細切れになっていき、メモリの断片化を引き起こしています。こうなってしまうとページの開放をしても意味はなくなり、全体的な物理メモリは足りているのにも関わらず、メモリ確保ができずシステムが止まってしまいます。これを防止するためには、スワッピングを有効化するか後述のメモリコンパクションを利用する必要があります。

参考

メモリコンパクション

2010年8月1日にリリースされたLinuxカーネル2.6.35で追加された比較的最近の機能です。ページマイグレーション(page migration)と呼ばれるページの移動処理を適切に行うことで連続した空き物理メモリを確保する手法であり、断片化した物理メモリを減らすことができます。この機能の構想自体はかなり前からあったようですが、慎重に取り込む必要があり、なかなか取り込まれなかった過去を持つようです。

物理メモリが少ないが長時間稼働させるサーバーや、スワッピング領域を使用しないような設定をすると、後述の断片化によってシステムが止まる問題を防止することが可能となります。

カーネルのconfigureで以下の設定を有効にしてコンパイルすることで有効化されます。

Allow for memory compaction (COMPACTION) [N/y/?] y
Page migration (MIGRATION) [Y/?] y

※メモリコンパクションを有効にした時点で、マイグレーションも自動的に有効化される。

Raspberry Piに搭載されているOSのRaspbianでは、2013年8月のチケットにより有効化されるようになりました。従って、これ以前のRaspbianを使用かつ、スワッピング領域を無効にしているOSを長期間サーバーとして使用していると、動作に支障をきたすと思われます。

この機能が有効になっているかどうかは、/proc/vmstatの情報を確認することで分かります。

[pi@centos-rpi2 ~]$ cat /proc/vmstat
nr_free_pages 3724
nr_zone_inactive_anon 6700
nr_zone_active_anon 6456
nr_zone_inactive_file 4213
nr_zone_active_file 10753
nr_zone_unevictable 0
nr_zone_write_pending 0
nr_mlock 0
nr_page_table_pages 426
nr_kernel_stack 928
nr_bounce 0
nr_zspages 0
nr_free_cma 30
nr_inactive_anon 6700
nr_active_anon 6456
nr_inactive_file 4213
nr_active_file 10753
nr_unevictable 0
nr_slab_reclaimable 194057
nr_slab_unreclaimable 8372
nr_isolated_anon 0
nr_isolated_file 0
workingset_refault 547547
workingset_activate 454721
workingset_nodereclaim 17371
nr_anon_pages 3650
nr_mapped 5947
nr_file_pages 24556
nr_dirty 0
nr_writeback 0
nr_writeback_temp 0
nr_shmem 9061
nr_shmem_hugepages 0
nr_shmem_pmdmapped 0
nr_anon_transparent_hugepages 0
nr_unstable 0
nr_vmscan_write 62458
nr_vmscan_immediate_reclaim 68
nr_dirtied 2071412
nr_written 2005220
nr_dirty_threshold 3446
nr_dirty_background_threshold 1721
pgpgin 1767078
pgpgout 14816894
pswpin 8249
pswpout 62458
pgalloc_normal 166023760
pgalloc_movable 0
allocstall_normal 61
allocstall_movable 26
pgskip_normal 0
pgskip_movable 0
pgfree 166149234
pgactivate 957876
pgdeactivate 1150022
pglazyfree 0
pgfault 166331547
pgmajfault 20747
pglazyfreed 0
pgrefill 1558500
pgsteal_kswapd 1145533
pgsteal_direct 5880
pgscan_kswapd 1237167
pgscan_direct 6432
pgscan_direct_throttle 0
pginodesteal 8
slabs_scanned 375784283
kswapd_inodesteal 21115
kswapd_low_wmark_hit_quickly 1070
kswapd_high_wmark_hit_quickly 172
pageoutrun 4604
pgrotated 63266
drop_pagecache 0
drop_slab 0
oom_kill 0
pgmigrate_success 117820
pgmigrate_fail 1646
compact_migrate_scanned 26687275
compact_free_scanned 9682567
compact_isolated 240690
compact_stall 16
compact_fail 14
compact_success 2
compact_daemon_wake 1022
compact_daemon_migrate_scanned 26678846
compact_daemon_free_scanned 9674528
unevictable_pgs_culled 0
unevictable_pgs_scanned 0
unevictable_pgs_rescued 0
unevictable_pgs_mlocked 0
unevictable_pgs_munlocked 0
unevictable_pgs_cleared 0
unevictable_pgs_stranded 0
swap_ra 3197
swap_ra_hit 2605

上記のようにcompactなんたらという情報がある場合は、有効化されています。

参考

物理メモリを確保する方法

物理メモリの確保についても解説します。

C言語にはmallocといったメモリを動的確保する命令が存在します。ただこの命令を呼んだからといって物理メモリが確保されるわけではなく、実際には仮想メモリが確保されることとなります。仮想メモリに値を書き込むことで、物理メモリが割り当てられて利用されることとなります。仮想メモリの場合は、アドレス上は連続していたとしても、内部の物理メモリは連続した領域になっていません。

以下、ユーザープロセスとデバイスドライバでの物理メモリ確保について解説します。

参考

ユーザーランド

ユーザー空間で動くプロセスでメモリを確保するには、mallocが利用可能です。ただし、上述のようにmallocでは、仮想メモリの確保となるため、任意の物理メモリの確保や、連続した物理メモリを意図的に確保することはできません。

Raspberry Piでは、以下のように特殊なデバイスノード/dev/memとアドレスをマッピングするmmapをすることで、任意の物理メモリのアドレス空間をユーザーランドで利用することが可能です。確保したいサイズは、4096の倍数でなければなりませんし、オフセットはアライメント調整が必要です。この方法は、GPIOなどの制御用レジスタを変更する場合の方法として紹介されており、他の物理メモリのアドレスを指定できるかは不明です。

int fd = open("/dev/mem", O_RDWR | O_SYNC);
void *tgt_mmap = mmap(NULL, 確保したいサイズ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, オフセット)

上記のように物理メモリの確保は難しいですが、仮想アドレスから物理アドレスを調べる方法はあるようです。具体的には、/proc/self/pagemapを利用して、中身のデータを解析して変換するようです。DPDKにあるrte_mem_virt2phy() という関数が参考になるようですが、詳細は、mm_iさんの「睡分不足 – 仮想アドレスから物理アドレスを求める」を確認するとよいです。

参考

カーネル

カーネル空間で動くドライバなどでは、仮想メモリを確保するvmallocと、物理メモリを確保するkmallocを使用することが可能です。このうちkmallocを使用することで、意図的に連続した物理メモリを確保することが可能です。

mmapのように意図した物理アドレスから指定したサイズまで利用する場合は、request_mem_regionが利用できるらしい。

参考

メモリが確保できなくなったらどうなるか

メモリが確保できなくなるパターンは2種類あり、それらによって発生する動作が異なります。私が認識している大きなパターンは以下の通りです。これ以外に知っている方はTw(略)

  • 物理メモリが足りていない
  • メモリの断片化(物理メモリが足りているが連続した空き領域が足りない)

それぞれについて解説していきます。

物理メモリが足りていない

物理メモリが足りていない状況です。この場合は、OOM Killerが発生します。OOM Killerとは、”Out Of Memory Killer”の略で、OSがメモリをたくさん使用しているユーザープロセスをOSが強制終了させてしまいます。

OOMキラーで殺されるかどうかは、色々な複雑な式で決められるようですが、OOMキラーで殺されないように優先度を変更することも可能です。具体的には、/proc/PID/oom_adjに優先度、-16から+15(大きいほうが殺されやすくなる)を書き込むことで変更が可能であり、-17を設定すると殺されることはなくなります。

参考

物理メモリが足りているが連続した空き領域が足りない

いわゆるメモリが断片化されている状態です。この状態に陥っているかどうかは、topコマンドでは確認できず、buddyinfoの情報を確認することで判別がつきます。具体的には、メモリの断片化状態に陥ると、4KBや8KBといった小さなページサイズの物理メモリはたくさんあるのにもかかわらず、128KBとかより大きなページサイズの物理メモリの数量が0になります。

メモリの断片化状態に陥っていると、スワッピング、ページの開放、メモリコンパクションの実行を行います。この実行の度にsyslogには “page allocation failure” が残ります。このログが残っているうちはまだなんとか動いていますが、もしこれらの処理をしてもメモリの断片化が解消されず、メモリを確保出来ない場合は、システムが落ちるという認識です。

なお、メモリの断片化は、buddyinfo情報から分かりますが、断片化度のような定量的な数値として出したい気持ちがあります。この断片化度を求める方法については、どこかの英語のサイトに書いてあった覚えがありますが、思い出したら記載します。たしか、buddyinfoの情報からいろいろ計算して出します。

参考

プロセス単体が使用している物理メモリを調べる

これまで、システム全体に着目していましたが、ここではプロセス単体に目を向けてみます。

PIDを確認する

プロセスの物理メモリを見るためには、/proc/PID/statusを確認するとよいです。

まずは、対象のプロセスのPIDを取得してみましょう。以下で確認できます。

top -b -n 1
か
ps -x

topの場合は、-bによりバッチモードで全プロセス表示を行い、-n 1で一度だけ表示ということになります。psの場合は、-xにより現在実行中プロセスのみを表示させることができます。

topで調べる場合は、そのプロセスの仮想メモリ使用率やCPU使用率が表示されます。psは、プロセスに渡している引数まで表示される特徴があります。場合に合わせて使い分けましょう。

参考

/proc/PID/statusを確認する

catで適当なプロセスのstatusを確認してみます。

[root@centos-rpi2 pi]# cat /proc/6496/status
Name:   node
Umask:  0022
State:  S (sleeping)
Tgid:   6496
Ngid:   0
Pid:    6496
PPid:   1
TracerPid:      0
Uid:    0       0       0       0
Gid:    0       0       0       0
FDSize: 256
Groups: 0
NStgid: 6496
NSpid:  6496
NSpgid: 6496
NSsid:  6473
VmPeak:    89460 kB
VmSize:    88952 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:     26544 kB
VmRSS:      9920 kB
RssAnon:            5100 kB
RssFile:            4820 kB
RssShmem:              0 kB
VmData:    52044 kB
VmStk:       132 kB
VmExe:     27212 kB
VmLib:      2880 kB
VmPTE:       110 kB
VmPMD:         0 kB
VmSwap:     2028 kB
Threads:        7
SigQ:   0/7345
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000001000
SigCgt: 0000000188004202
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
NoNewPrivs:     0
Seccomp:        0
Speculation_Store_Bypass:       unknown
Cpus_allowed:   f
Cpus_allowed_list:      0-3
Mems_allowed:   1
Mems_allowed_list:      0
voluntary_ctxt_switches:        1534
nonvoluntary_ctxt_switches:     330

ここで、仮想メモリ及び、物理メモリに関わる部分は以下の2つです。

  • VmPeak
  • VmSize
  • VmHWM
  • VmRSS

VmPeak

プロセスが実行中に最大で使用した仮想メモリ使用量です。

VmSize

今現在プロセスが使用している仮想メモリ使用量です。

VmHWM

プロセスが実行中に最大で使用した物理メモリ使用量です。

VmRSS

今現在プロセスが使用している物理メモリ使用量です。

参考

プロセスのメモリリークを調査する

mtraceを使うと分かるらしいのですが、現在調査中です。分かったら色々追記したいと思います。

参考

おわりに

少し長くなってきたので、いったんここまででLinuxと物理メモリの話は休憩です。また私の方で知識が増えたり、気がつきがあったら内容を直したら追記していきたいと思います。

以上、お疲れさまでした!

コメント

タイトルとURLをコピーしました