1.はじめに
1-1.この記事の要旨
psコマンドのVSZ(仮想メモリ)、RSS(物理メモリ)の挙動について質問を受けたので、簡単な検証プログラムを作ってmalloc/freeのメモリ確保/解放や、データの読み込み・書き込みとVSZ/RSSの関係性及び、freeコマンドとmeminfo情報でシステムワイドなメモリの挙動について確認しました。
検証結果から以下の挙動を実機で確認しました。
1-2.(予習)メモリに関する指標とlinuxのメモリ挙動について
この記事に出てくる、メモリに関する指標値の説明です。
- psコマンド
- freeコマンド
- meminfo
- MemFree : 未使用のメモリ量です。freeコマンドの"free値"の元になっています。
- AnonymousPage(無名ページ): swapアウト対象となるページ。ざっくりというとプロセスが普通使っているメモリ。
- FilePage(ファイルページ):メモリ不足時、解放されるページ。ざっくりいうとファイルキャッシュのメモリ。
- active: AnonymousPage/FilePageのうち、比較的最近アクセス(read or write)されたページ。swapアウトや解放されないページではない。*3
- inactive:AnonymousPage/FilePageのうち、しばらくアクセスされていないページ
2.検証環境と検証方法
2-2.検証方法
下記動作を行う検証プログラムを作成し実行します。なお検証をわかりやすくするために、動作の間に10秒のsleepをかけています。
- malloc()で512MBのメモリを確保する
- 確保した512MBのメモリ領域を20秒かけてreadする(1秒間隔で1/20のサイズずつ20回readする)
- 同じ領域に、20秒かけてデータを書き込む
- もう一度同じ領域を、20秒かけて読み込む
- free()で確保したメモリを解放する
プログラムは以下のリンク先にありますので、これをgccで"gcc verifying_memory.c"とかしてビルドします。
Verifying on Memory behavior · GitHub
ちなみにglibcのmalloc()は、サイズが128kb(変更可)より大きい場合はmmap()取得になります。*4
ですので512MB確保している今回の検証は、必ずmmap()になります。
2-3.測定方法
(1)psコマンドによるVSZ,RSS情報の取得
簡単ですが、下記のワンライナーコマンドで1秒間隔で取得しました。(検証プログラムのファイル名は、"a.out")
echo -n 'DATE '; ps -aux|head -n 1;while true;do printf '%s ' "`env LANG=C date '+%X.%N'`";env LANG=C ps -aux|grep -e a.out|grep -v grep;sleep 1;done
(2)freeコマンドとmeminfo情報の取得
こちらの記事のシェルで取得しました。
freeコマンドとmeminfoを取得してCSV形式で保存するシェルスクリプト - のぴぴのメモ
3.結果
3-1.全体の結果
psコマンドのVSZ/RSS、freeコマンド、meminfoの推移を並べると以下のようになります。
3-2.プロセスのVSZ/RSS挙動
ポイント① malloc()した時の挙動→VSZのみ増加
malloc()で512MBを取得したタイミングで、VSZが増加しているのがわかると思います。
一方RSSは、malloc()しただけでは増えてなく、このタイミングでは物理メモリの割り当てが発生していないことがわかります。(次のfreeコマンドやmeminfoの結果と照らし合わせて見るとよりわかりやすいです。)
ポイント② 1回目のデータread時→RSSは増えない
malloc()した後、そのデータをreadしても、物理メモリの割り当ては発生しません。そのため、RSSも増加しません。
実はプロセスがカーネルからメモリ確保したタイミングでは、確保した仮想メモリのページは、”zero page”という1ページがすべて0データで埋められた特殊なページにマッピングされており、データをreadした時は、その"zero page"の値を参照するためです。*5
ポイント③ データwrite→RSSが増加する
データのwriteが発生した時点で、RSSが増加していることがわかります。つまりデータへのwriteのタイミングで、そのwriteした仮想メモリのページに、晴れて物理メモリが割り当てられたということです。
これはカーネルのCOW(Copy-On-Write)という手法で、mallocなどでメモリを確保したタイミングでは実際には物理メモリの割り当てはせず、必要になったタイミングで初めて物理メモリを割り当ているからです。*6
もう少し詳しく書くと、先ほど説明したzero pageは書き込み禁止設定がされており、そこにデータを書こうとするとページフォルトが発生します。ページフォルトが発生すると、ページフォルトの割り込み処理の中にあるCOWの実装で、zero pageの内容を新しいページにコピーしして、そのページを改めて仮想ページとマッピングします。その時にRSSが+1ページ加算されます。
3-3.システムワイドな挙動(freeコマンド/meminfo)
ポイント① malloc()した時の挙動→usedもAnonymousPageも増えない
malloc()した時には、RSSが増えてない、つまり物理メモリへのマッピングがされていないため、freeコマンドやmeminfoでシステムワイドで見た物理メモリの利用状況でも、変化はありません。
ポイント②1回目のデータread時→変化しない。
同じですね。物理メモリの割り当てが発生しないため、freeコマンドやmeminfoも変化はありません。
ポイント③ データwrite→used上昇、AnonymousPage上昇
Writeするタイミングで、物理メモリの割り当てが発生するため、Usedが上昇します。meminfoでさらに詳しく見るとその上昇しているところが、AnonymousPage(無名ページ)であることが確認できます。
4.参考ページ
*1:kernelは、”MemTotal - MemFree - AnonymousePage(Active/InActive) - FilePage(Active/InActive) - Unevictable+Mlocked”で算出しています。
*2:"total - free - buffers - cache"で計算された値になります。RHEL6まではbuffer/cacheを含んでいましたが、RHEL7からはbuffer/cacheが含まれない値になりました。
*3:/proc/meminfo の Inactive は利用可能なメモリ領域ではない - ablog
*4:glibcのmalloc()は、指定するメモリサイズが小さい場合(閾値はMMAP_THRESHOLDで設定されており、デフォルトは128kb)は、Java vmのヒープのように、カーネルから取得済みで未使用となったメモリ領域をglibc内で保持して再利用する実装があるため、malloc()により必ずしもVSZが増加するとは限りません。詳しくはmalloc()のmanの注意(NOTES)を参照。
*5:memory management - Linux kernel: Role of zero page allocation at paging_init time - Stack Overflow