のぴぴのメモ

自分用のLinuxとかの技術メモ

プロセスのVSZ,RSSとfree,meminfoの関係を実機で確認する

1.はじめに

1-1.この記事の要旨

psコマンドのVSZ(仮想メモリ)、RSS(物理メモリ)の挙動について質問を受けたので、簡単な検証プログラムを作ってmalloc/freeのメモリ確保/解放や、データの読み込み・書き込みとVSZ/RSSの関係性及び、freeコマンドとmeminfo情報でシステムワイドなメモリの挙動について確認しました。
検証結果から以下の挙動を実機で確認しました。

  • 検証結果
    1. malloc()の時点では、VSZのみ増加し、RSSやfreeコマンドのusedは増加しない
    2. データreadでは、物理メモリの割り当てが発生しないため、RSSとfreeコマンドのusedは増加しない(COW(Copy-On-Write)実装によるもの)
    3. データwriteして初めて、物理メモリの割り当てが発生し、RSSとfreeコマンドのusedが増加する

f:id:nopipi:20171115024528p:plain
psコマンドのVSZ/RSS挙動
f:id:nopipi:20171115024541p:plain
freeコマンドのused, free,availableの挙動
f:id:nopipi:20171115024535p:plain
meminfoの挙動*1

1-2.(予習)メモリに関する指標とlinuxのメモリ挙動について

この記事に出てくる、メモリに関する指標値の説明です。

  • psコマンド
    • VSZ : プロセスが確保している仮想メモリサイズ
    • RSS : プロセスが確保している物理メモリサイズ
  • freeコマンド
    • used : カーネルや各プロセスが利用しているメモリの総量です。*2
    • buffer /cache: キャッシュやバッファとして利用されいるメモリ量です。(この記事では、単にbufferと記載しています)
    • free : 未使用のメモリ総量です。
  • meminfo
    • MemFree : 未使用のメモリ量です。freeコマンドの"free値"の元になっています。
    • AnonymousPage(無名ページ): swapアウト対象となるページ。ざっくりというとプロセスが普通使っているメモリ。
    • FilePage(ファイルページ):メモリ不足時、解放されるページ。ざっくりいうとファイルキャッシュのメモリ。
    • active: AnonymousPage/FilePageのうち、比較的最近アクセス(read or write)されたページ。swapアウトや解放されないページではない。*3
    • inactive:AnonymousPage/FilePageのうち、しばらくアクセスされていないページ

2.検証環境と検証方法

2-1.検証環境

検証環境は以下の通りです。

  • RHEL7.4(3.10.0-693.el7.x86_64)
  • サーバ構成: CPU 1core、メモリ 2GB(VirtualBox利用)

2-2.検証方法

下記動作を行う検証プログラムを作成し実行します。なお検証をわかりやすくするために、動作の間に10秒のsleepをかけています。

  1. malloc()で512MBのメモリを確保する
  2. 確保した512MBのメモリ領域を20秒かけてreadする(1秒間隔で1/20のサイズずつ20回readする)
  3. 同じ領域に、20秒かけてデータを書き込む
  4. もう一度同じ領域を、20秒かけて読み込む
  5. free()で確保したメモリを解放する

プログラムは以下のリンク先にありますので、これをgccで"gcc verifying_memory.c"とかしてビルドします。
Verifying on Memory behavior · GitHub
ちなみにglibcmalloc()は、サイズが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の推移を並べると以下のようになります。

f:id:nopipi:20171115024528p:plain
psコマンドのVSZ/RSS挙動
f:id:nopipi:20171115024541p:plain
freeコマンドのused, free,availableの挙動
f:id:nopipi:20171115024535p:plain
meminfoの挙動

3-2.プロセスのVSZ/RSS挙動

f:id:nopipi:20171115024528p:plain
psコマンドの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)

f:id:nopipi:20171115024541p:plain
freeコマンドのused, free,availableの挙動
f:id:nopipi:20171115024535p:plain
meminfoの挙動

ポイント① malloc()した時の挙動→usedもAnonymousPageも増えない

malloc()した時には、RSSが増えてない、つまり物理メモリへのマッピングがされていないため、freeコマンドやmeminfoでシステムワイドで見た物理メモリの利用状況でも、変化はありません。

ポイント②1回目のデータread時→変化しない。

同じですね。物理メモリの割り当てが発生しないため、freeコマンドやmeminfoも変化はありません。

ポイント③ データwrite→used上昇、AnonymousPage上昇

Writeするタイミングで、物理メモリの割り当てが発生するため、Usedが上昇します。meminfoでさらに詳しく見るとその上昇しているところが、AnonymousPage(無名ページ)であることが確認できます。

*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:glibcmalloc()は、指定するメモリサイズが小さい場合(閾値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

*6:コピーオンライト - Wikipedia

Linuxのclock_gettime()でナノ秒の時刻を取得し表示する

1.はじめに

clock_gettime()で時刻を取得し時刻をナノ秒で表示するサンプルです。時刻取得といえばgettimeofday()ですが、POSIXではgettimeofdayは廃止予定で、clock_gettime()の利用を推奨しているので、こちらを利用しています。

2.コードと実行例

(1)コード

#include <stdio.h>
#include <time.h>
int main(){
	struct timespec ts;
	struct tm tm;
	// 時刻の取得
	clock_gettime(CLOCK_REALTIME, &ts);  //時刻の取得
	localtime_r( &ts.tv_sec, &tm);       //取得時刻をローカル時間に変換
	// 出力
	printf("tv_sec=%ld  tv_nsec=%ld\n",ts.tv_sec,ts.tv_nsec);
	printf("%d/%02d/%02d %02d:%02d:%02d.%09ld\n",
		tm.tm_year+1900,
		tm.tm_mon+1,
		tm.tm_mday,
		tm.tm_hour,
		tm.tm_min,
		tm.tm_sec,
		ts.tv_nsec);
	return(0);
}

(2)実行例

$ gcc sample.c
 $./a.out 
tv_sec=1508516823  tv_nsec=736416218
2017/10/21 01:27:03.736416218

RHEL7でのビルド&実行例です。version 2.17より前のgccの場合はリンク時に”-lrt”オプションを付与する必要があります。(多分、RHEL5以前)

3.説明

(1)clock_gettime(CLOCK_REALTIME, &ts)

時刻を取得します。最初の引数は取得する時刻の種類を指定しており、"CLOCK_REALTIME"はシステム全体で一意な精度の高い実時間情報を取得します。取得した情報は、2つ目の引数のtimespec構造体に格納されます。

struct timespec {
	time_t   tv_sec;       ==> 1970年1月1日からの秒数が格納されます。
	long     tv_nsec;      ==> 秒未満の時刻(ナノ秒)で格納されます。
};

(2)localtime_r( &ts.tv_sec, &tm)

取得した時刻のうち、tv_sec(1970年1月1日からの秒数)を年/月/日/時/分/秒に変換し、tm構造体に格納します。
localtime_r()は、localtime()と同じ処理になりますが、localtime()はスレッドセーフでなく、localtime_r()はスレッドセーフな実装になります。スレッドセーフでないlocaltime()はマルチスレッド環境で利用すると想定外の挙動を起こす可能性があるため、今回のサンプルではマルチスレッドでも使えるように、スレッドセーフなlocaltime_r()を利用しています。

(3)printfで出力

localtime_rで変換した時刻情報を、printf()で出力します。秒未満は、timespecのtv_nsecに格納されているので、tv_nsecを使って秒の小数点以下の値を出力しています。


f:id:nopipi:20171028095337p:plain
clock_gettime()で時刻取得して変換出力する概要

最小構成でmanがない場合の対処方法->man-pagesをインストール

結論

タイトル通りですが、備忘録です。
CentOSRHELで最小構成でインストールした場合、最小限のコマンドマニュアルしかなくて、man(2)、man(3)・・・は、入っていないのですね。
結論から言うと、そんな時は"man-pages"(日本語も必要な場合は、man-pages-jaを追加で)のパッケージをインストールするとman見れるようになります。

yum install man-pages man-pages-ja

お世話になったページ

おそらく同じ悩みをした人がいるとググってヒットした、こちらのページを参考にしました。
hyperneetprogrammer.hatenablog.com
記事は苦労が伺える内容になっていますが、結論だけ使わせていただきました。すみません。先人の方の努力に感謝します。
リンク先記事は、CentOS6.4ですが、RHEL7でも問題なくmanが使えるようになりました。