コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
ページングとスワップ
「必要になった分だけ」に、誰が気づくのか
確保しただけで一度も触っていない領域には、RAM が割り当てられていませんでした。ではプログラムがその領域を初めて触った瞬間、誰がそれに気づくのでしょうか。プログラム自身は普通に代入文を実行しただけで、何も申告していません。
気づくのは CPU です。翻訳表を引きにいって「この範囲には物理アドレスが結びついていない」と分かると、CPU は命令の実行を途中で止め、OS を呼び出します。これが ページフォールト です。OS は空いている RAM を 1 つ選んで表に書き込み、止めた命令を最初からやり直させます。プログラムから見ると、何事もなく代入が終わったように見えます。
つまり「使うまで割り当てない」は、事前の申告ではなく、触った瞬間の割り込みで実現されています。
4KB ずつ束ねる理由
翻訳表は 1 バイトごとには作れません。8GB 分の対応を 1 バイト単位で持つと、表そのものが RAM を食い潰します。そこで仮想側も物理側も ページ という固定サイズの塊にまとめ、ページ単位で対応づけます。多くの環境で 4KB です。
4KB なら 8GB でも 200 万行程度に収まります。代わりに、1 バイトだけ使いたいときでも 4KB が丸ごと割り当てられます。この端数の無駄を承知のうえで、表を現実的な大きさに抑えているわけです。
足りなくなったら、使っていないものを追い出す
RAM が埋まってくると、OS は当分使われそうにないページをディスク上の スワップ領域 へ書き出し、その物理ページを空けます。追い出されたページを後から触ると、またページフォールトが起き、今度はディスクから読み戻されます。
どれを追い出すかは、素直に考えれば「最後に使われたのが一番古いもの」です。この方針を LRU と呼びます。ただし全ページの最終アクセス時刻を正確に記録し続けると、記録のコストのほうが高くつきます。実際の OS は、ハードウェアが自動で立てる参照ビットを定期的に見て回り、最近触られていないものを近似的に選びます。
追い出しすぎると、何も進まなくなる
困るのは、追い出したページがすぐまた必要になる場合です。読み戻すために別のページを追い出し、そのページもすぐ必要になり、を延々と繰り返します。RAM の 100 ナノ秒に対してディスクは 10 万ナノ秒なので、この状態に入るとシステムは体感で止まります。スラッシング と呼びます。
同じページフォールトでも、空いている RAM を表に書き足すだけで済むものと、ディスクから読み戻さないと進めないものでは重さが桁違いです。後者をメジャーフォールトと呼びます。/proc/vmstat の pgmajfault が増え続けているなら、スワップに手を出している証拠です。
ターミナル
vmstat 1si と so の列がスワップの出入りです。ここが常に 0 なら平穏で、値が立ち続けているならスラッシングを疑います。DB サーバーで vm.swappiness=1 を設定してスワップをほぼ止めるのは、際限なく遅くなるくらいなら潔く落ちてほしい、という判断です。いつ返るか分からないシステムは、止まっているのと大差ありません。