TT Lab
はじめる
学ぶ 学習パス コース

オペレーティングシステム

プロセス — OSが売っている隔離という商品

TT Labで続きを見る

一言でいうと

プロセスは、「このプログラムは自分専用のコンピューターを持っている」という錯覚をOSが作って売る商品であり、その錯覚の核心は独立したアドレス空間です。

なぜ必要なのか

1台のマシンで複数のプログラムを動かすには、2つのことを保証しなければなりません。1つ目は、あるプログラムのバグが別のプログラムのメモリを壊さないことです。2つ目は、プログラムが自分がいつ止まり、いつ再開されるかを気にしなくてよいことです。

この2つを同時に提供する抽象がプロセスです。各プロセスは0番地から始まる自分専用の仮想アドレス空間を持ち、CPUをしばらく借りては返しますが、そのことに気づきません。

どう動くのか

OSはプロセスごとに、PCB(Process Control Block)を保持します。ここに入るのは、おおよそ次のものです。

コンテキストスイッチは、現在のプロセスのレジスターをPCBに保存し、次のプロセスのものを復元する作業です。レジスターを数十個動かすこと自体は短時間で済みますが、本当のコストはその後に来ます。新しいプロセスが実行を始めると、キャッシュやTLBに残っていた前のプロセスの内容が役に立たなくなり、ミスが大量に発生します。この間接コストは、直接コストよりはるかに大きくなります。

Linuxでのプロセスの作り方にも特徴があります。fork()は親をまるごと複製し、exec()がその上に新しいプログラムを上書きします。まるごと複製すると聞くと怖く感じますが、実際にはコピーオンライト(copy-on-write)で処理されます。親と子が同じ物理ページを共有し、誰かが書き込もうとした瞬間に、そのページだけをコピーします。すぐにexec()を呼ぶよくあるパターンでは、コピーはほとんど起きません。

プロセスの状態遷移でよく混乱するのがゾンビです。子が終了したのに、親がまだwait()で終了状態を刈り取っていないと、PCBだけが残ったゾンビになります。ゾンビはメモリをほとんど使いませんが、プロセステーブルのエントリを占有するので、溜まると新しいプロセスを作れません。逆に親が先に死ぬと、子は孤児プロセスになり、init(またはコンテナの1番プロセス)に引き取られます。コンテナの中でアプリケーションを1番プロセスとして起動したときにゾンビが溜まる事故は、ここから生まれます。1番プロセスには孤児を刈り取る責任がありますが、通常のアプリケーションはそのコードを持っていません。

現場での姿

ps出力のSTAT列にZがあれば、上の状況です。コンテナイメージにtiniのような小さなinitを入れるか、ランタイムのinitオプションを有効にするのが標準的な対応です。D状態(割り込み不可の待機)も知っておく価値があります。たいていはディスクやネットワークファイルシステムのI/Oを待っている最中で、この状態のプロセスはkill -9でもすぐには死にません。

続くクイズで確認すること

forkが高コストに見えるのに実際は安く済む理由と、コンテキストスイッチの本当のコストがどこにあるのかを説明できるか確認します。