はじめに
ラズパイなど組み込み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 ...
実は物理メモリは、LowMemoryとHighMemoryに分かれており、NormalがLowMemoryに相当します。
このうちLinuxのシステムが使用しているカーネル空間で確保できる物理メモリは、基本的にLowMemoryからしか確保ができません※。HighMemoryは、プロセスごとが持つユーザー空間からでしか利用できないのです。従って、HighMemoryが残ってたとしても、LowMemoryが少なければシステムが不安定な状況になります。
※正確に言うと、カーネルで利用できるkmalloc
でGFP_HIGHUSER
を指定すれば利用が可能ですが、細かいことは不明です。
参考
topとfreeとbuddyinfoとmeminfoから分かる空き物理メモリ
情報の読み方について
メモリの情報を確認する場合に、top
、free
、buddyinfo
、meminfo
といった情報があるのですが、これらは一体どのような情報がとれるのでしょうか。
ここでは物理メモリに着目してとれる情報を確認していきます。なお、取れた情報の中にスワップサイズも含まれますが、これは後程覚えていたら解説します。
実際に実行してみる
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)
※スワッピングについては後程、解説します。
参考
- enakai00 – /proc/meminfoを考える めもめも
- nopipi – 【RHEL】linuxメモリのfreeとmeminfoの関係を図解し利用率の計算方法を説明してみる (id:nopipi) はてなブログPro
- yohei-a – /proc/meminfo の Inactive は利用可能なメモリ領域ではない
キャッシュの開放タイミング
OSは適時に開放処理を行います。
min_free_kbytesの設定
開放処理は、min_free_kbytes
の設定が関係します。この情報は、/proc/sys/vm/min_free_kbytes
を見るか、カーネルパラメータ操作用のsysctl
でvm.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
なんたらという情報がある場合は、有効化されています。
参考
- Linux Weekly News – Memory compaction [LWN.net]
- @IT atmarkit – 1月版 無視できないフラグメンテーション問題への解答は?
- OSDNMagazin – Linuxカーネル2.6.35リリース、ネットワーク負荷軽減機構やH.264ハードウェアデコードなどをサポート
- kernel panic – Linux oom situation – Server Fault
- raspberrypi/linux – Raspbian: swapper page allocation failure #153
- raspberrypi/linux – Added CONFIG_COMPACTION=y to defconfigs #349
物理メモリを確保する方法
物理メモリの確保についても解説します。
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さんの「睡分不足 – 仮想アドレスから物理アドレスを求める」を確認するとよいです。
参考
- Man page of MALLOC
- Man page of MMAP
- 組み込みLinuxシステムとは
- Linuxの備忘録とか・・・ – /dev/mem
- 睡分不足 – 仮想アドレスから物理アドレスを求める
- www.kernel.org – pagemap, from the userspace perspective
カーネル
カーネル空間で動くドライバなどでは、仮想メモリを確保する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と物理メモリの話は休憩です。また私の方で知識が増えたり、気がつきがあったら内容を直したら追記していきたいと思います。
以上、お疲れさまでした!
コメント