tmpfs は本当に容量が動的なのか

Linux には tmpfs という便利なファイルシステムがあります。

$ mount -t tmpfs -o size=64m tmpfs /dev/shm
$ mount -t tmpfs -o size=64m /dev/shm /var/tmp

とすると、/var/tmp がディスク上ではなくメモリ上に作られたファイルシステムとして mount されます。なので、/var/tmp は I/O 時にディスクI/Oが一切発生しない高速なディスクとして使えると。いわゆる RAM ディスク。(もちろんサーバーの電源を落とすと保存したファイルは消えます。)

この tmpfs はなかなかに便利で、キャッシュとかそういうものでディスクにおいてたものここ置くと、ディスク I/O がカットできて超高速になります。はてなでは MySQL のスレーブの MyISAM のファイルを tmpfs において、オンメモリなデータベースを作って使ったりということも結構してます。(スレーブなら万が一サーバーが落ちてデータがなくなってもすぐ復帰できるので OK と。)

Linux で RAM ディスクを実現するためのファイルシステムは tmpfs 以外にも ramfs というのがあります。問題はこの tmpfs と ramfs の違い。

Google で tmpfs で検索すると、容量が動的に変化するRAMディスクを使うにはという記事がヒットします。で、ここには

仮想メモリベースのファイルシステムであるtmpfsを使用すれば、必要なサイズに応じてRAMディスクの容量が動的に変化するため、メモリを効率よく使用できる。

という記述があり、容量固定の ramfs に対して tmpfs は容量が伸縮するみたいに読めます。そういう風に理解している人は多いと思います。僕もそうです。

  • 2GB の物理メモリを積んだマシンがあって、ramfs で 1.5GB 確保すると、OS が使えるのは残りの 500MB。そうなるのが ramfs。
  • 2GB の物理メモリを積んだマシンがあって、tmpfs で 1.5GB 確保しても、OS が使えるのは 2GB。tmpfs に 700MB 書き込むと、OS が使えるのは残り 1.3GB になる。

という風に理解していました。

が、どうもその挙動がそうなのかどうなのかが怪しい。Linux に添付されてるドキュメントが JF にあったのでちょっと引用します。

If you compare it to ramfs (which was the template to create tmpfs) you gain swapping and limit checking. Another similar thing is the RAM disk (/dev/ram*), which simulates a fixed size hard disk in physical RAM, where you have to create an ordinary filesystem on top. Ramdisks cannot swap and you do not have the possibility to resize them.

(tmpfs を作成するテンプレートであった) ramfs と比較すると、スワップと、制限チェックに利点があります。もうひとつ別の良く似たものは、RAM ディスク( /dev/ram ) で、物理RAM 内にサイズを固定したハードディスクを、シミュレートしますが、最上位に、通常のファイルシステムを作成しなければなりません。RAM ディスクはスワップできませんし、サイズの変更もできません。

確かに tmpfs の方が ramfs よりも動的な性質を持っているとはあるのですが、それはスワップできるという点であって、「tmpfs で作成した領域の空いてるところは OS 側が使える」みたいなことは書いてありませんでした。

また、

tmpfsとramfsの機能は似ていますが、tmpfsはページアウトされるのに対し、ramfsはページアウトされない点が異なります。

とオラクル通信の記事でも tmpfs と ramfs の違いはページアウトの有無(スワップするかしないか)だと言及されてます。

  • tmpfs は OS がメモリが足りなくなったときにスワップして、OS 側にメモリを確保させる機能がある

ということは分かるのですが、tmpfs として確保した領域のうち空いてる箇所は果たして

  • tmpfs として確保した領域のうち使用されてないものは OS がアプリケーションに割り当てる
  • tmpfs として確保されたものはあくまでファイルシステムとして扱われ、OS がアプリケーションに割り当てることはない

のどちらなのかがはっきりしません。

そもそもこの疑問を持つきっかけがちょうど昨日ありまして。とあるサーバーの tmpfs の領域を拡張したのですが、スワップが発生したので tmpfs の領域を減らしてみたところスワップがなくなった、というケースが実際に発生したんです。この状況を見るに、tmpfs の空き領域は OS がアプリケーションに割り当てられない、という風に見れてしまいます。

で、もう少し検証するべく id:danjou に実験してもらいました。物理メモリ1GBを積んでるマシンに1GBの tmpfs 領域を確保して、どういうことになるか。

$ sudo mount -t tmpfs -o remount,size=1024m tmpfs /dev/shm
$ sudo mount -t tmpfs -o size=1024m /dev/shm /home/danjou/tmp

こんな感じでtmpfsを作って、その時点のdfとfree。

$ free
             total       used       free     shared    buffers     cached
Mem:       1002952      97600     905352          0       6500      46828
-/+ buffers/cache:      44272     958680
Swap:      2031608          0    2031608

メモリは 1GB でスワップは一切使っていない。で、メモリの空き容量(free)は 900 MB ある。つまり、tmpfs で確保してる領域も「空きメモリ」として換算されていると見れます。

$ df
Filesystem           1K-ブロック    使用   使用可 使用% マウント位置
/dev/mapper/VolGroup00-LogVol00
                      35740376   1534852  32360688   5% /
/dev/hda1               101086      9950     85917  11% /boot
/dev/shm               1048576         0   1048576   0% /dev/shm
/dev/shm               1048576         4   1048572   1% /home/danjou/tmp

これは df の結果。tmpfs で mount した領域はもちろんほとんど使用してない。(ちょっとしたファイルを置いてあってそれが 4bytes だった様子)

この状態から dd で tmpfs にでかいファイルを作っていって、スワップの発生状況を見る。つまり、

$ dd if=/dev/zero of=/home/danjou/tmp/zero count=1024M

これをやりながら vmstat で1秒おきにスワップ I/O を見て行くと。結果はこんな感じ。

procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in    cs us sy id wa
 0  0      0 903508   6636  47368    0    0     0     0 1008    25  0  0 100  0
 1  0      0 795600   6636 155076    0    0     0     0 1033    36 12 49 38  0
 1  0      0 615304   6636 335016    0    0     0     0 1011    12 21 79  0  0
 1  0      0 435256   6636 514692    0    0     0     0 1027    27 21 79  0  0
 1  0      0 255208   6636 694300    0    0     0     0 1008    11 18 82  0  0
 1  0      0  75532   6636 873636    0    0     0     0 1023    27 23 77  0  0
 0  3   3864  10792   1724 938992    0    0     0    60 1016    84 11 46  0 44
 1  4  55980  10876     64 889272    0  768     0   768 1029   149  0  4  0 96
 1  1 187092  10976     64 759104    0  896     0   896 1011   170  1  5  0 94
 0  3 430128  10872     64 517548    0 1460     0  1460 1033   193  0  6  0 94
 0  4 681312  10680     64 267632    0 1356     0  1356 1014   133  0  5  0 95
 0  2 841800  10420     64 110308    0 19636     0 19636 1037   201  1  7  0 92
 0  4 854548  11168     64 102244    0 74420     0 74420 1016   125  1  7  0 92
 3  2 862244  14152     64  95552    0 50584     0 50584 1047    82  0 65  0 35
 2  2 862452  28720     64  95344    0 4744     0  4744 1017    15  0 100  0  0
 3  1 863100  10916     64 124964    0 14764     0 14764 1035    51  3 97  0  0
 2  1 863676  10916     64 134912    0 10888     0 10888 1024    60  1 99  0  0
 2  2 864076  15008     64 146280    0 9948     0  9948 1018    39  2 98  0  0
 3  2 864276  10792     64 159844    0 5144     0  5144 1033    58  1 99  0  0
 2  2 868280  11388     64 163068    0 61056     0 61056 1025    53  1 99  0  0
 2  3 868860  10396     68 172464    0 1728     4  1728 1021    29  1 99  0  0
 2  3 869484  14268     76 181948    0 4268     8  4268 1045    36  2 98  0  0
 3  2 869968  17876     92 187880    0  956    12   956 1031    25  1 99  0  0
 0  2 869968  16388    100 190704    0    4    40     4 1028    29  0 16  0 84
 0  2 869968  17132    108 190704    0    0     8     0 1021    23  0  0  0 100

「tmpfs の空き領域はアプリケーションに割り当てられない」という仮定からくと dd しはじめてすぐにスワップしそうなもんですが、そうではなく物理メモリの空き容量が徐々に減っていって、そこで初めてスワップが発生してます。やっぱり、tmpfs の空き領域は純粋に空きメモリとして扱われる、ということでしょうか。

この dd が終わったあとの free と df ですが、

$ df
Filesystem           1K-ブロック    使用   使用可 使用% マウント位置
/dev/mapper/VolGroup00-LogVol00
                      35740376   1534856  32360684   5% /
/dev/hda1               101086      9950     85917  11% /boot
/dev/shm               1048576         0   1048576   0% /dev/shm
/dev/shm               1048576   1048576         0 100% /home/danjou/tmp

tmpfs 領域はきっちり 100% 使っていて、

$ free
             total       used       free     shared    buffers     cached
Mem:       1002952     977660      25292          0        336     191648
-/+ buffers/cache:     785676     217276
Swap:      2031608     869968    1161640

free の結果スワップが 870MB弱使われた状態になっているのが分かります。おそらくこれは dd で作った、tmpfs に置かれたファイルのうちの多くの部分がスワップ領域に退避させられている、ということでしょう。実際、

$ sudo umount /home/danjou/tmp
$ free
             total       used       free     shared    buffers     cached
Mem:       1002952     351380     651572          0      13628     273932
-/+ buffers/cache:      63820     939132
Swap:      2031608        856    2030752

と umount してから free を観ると、スワップ領域にあったものがごっそりなくなるのが確認できます。また、物理メモリもかなり開放されて空き領域へ移ります。

この実験の結果からは

  • tmpfs が、OS のメモリが足りなくなったらスワップに移る
  • tmpfs の空き領域は @IT の記事が解説しているように、空きメモリとして扱われアプリケーションに割り当てられる

ということが分かります。僕の理解が考察ってなければ。多分。断言する自信はなし。

ということで、なぜそうドキュメントに書いてないのか、あと、昨日経験したスワップ発生ケースは一体どういうことなのかが説明できない、というのが気持ち悪いまま実験が終わってしまいました。いっそのこと空き容量になってる部分は使えないとなってくれたほうがすっきりしたんだけど。

というわけで自力で調べられるのはここらへんまで。もしカーネルの実装がどうなってるとか、tmpfs 周りに詳しい人とかいたら是非答えを教えてください。