コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
プロセスとは
同じプログラムを 2 つ起動しても、変数がぶつからない
ターミナルを 2 枚開いて、どちらでも同じ Python スクリプトを走らせたとします。中で count = 0 としているのに、片方で増やした値がもう片方に漏れることはありません。同じファイル、同じ変数名、同じ機械なのにです。
理由は、起動するたびに別の家が建っているからです。ディスクに置いてあるプログラムは設計図にすぎず、それをメモリに読み込んで実行を始めた瞬間に、1 軒ぶんの実体ができます。これがプロセスです。設計図は 1 枚でも、家は何軒でも建ちます。そして家と家の間には壁があり、隣の部屋を覗くことはできません。
カーネルは 1 軒につき 1 枚の台帳を持つ
家を建てた以上、管理台帳が要ります。カーネルはプロセスごとに 1 枚の記録を持ち、そこに住所と持ち物を書いています。番地に相当するのが PID という一意な整数で、誰が建てたかを示す親の PID も並んで載ります。
台帳に載るのは番号だけではありません。メモリのどこからどこまでを使ってよいかという割り当て、開いているファイルやソケットの一覧、いま走っているのか入出力を待っているのか終わったのかという状態、そして順番待ちでの優先度です。プロセスを止めたり、再開したり、後片付けしたりできるのは、この 1 枚があるからです。
状態が複数あるのは、走れる状態と走っている状態が違うからです。順番待ちの列に並んでいるだけで走っていないもの、ディスクの返事を待っていて呼ばれても走れないもの、実際に CPU を握っているもの。この区別があるので、入出力を待っている相手を順番から外して、走れる相手だけを呼ぶことができます。
終わった直後のプロセスが一瞬だけ残ることがあります。親が結果を受け取るまで台帳を捨てられないためで、ps で Z と出るのがこの状態です。親が受け取り忘れると台帳が溜まり続けます。
建ててから、中身を入れ替える
新しいプログラムを動かすとき、POSIX 系の OS は更地に家を建てません。いま動いている自分をそっくり複製して、複製したほうの中身を入れ替えます。
c
pid_t pid = fork(); // 自分と同じ家をもう 1 軒
if (pid == 0) {
execve("/bin/ls", (char *[]){"ls", NULL}, environ); // 中身を ls に
} else {
int status;
waitpid(pid, &status, 0); // 子が終わるのを待つ
}複製と聞くとメモリを丸ごとコピーしているように思えますが、実際にコピーされるのは書き換えが起きたところだけです。だから複製そのものは見た目ほど高くつきません。
この親子関係は機械の中で 1 本の木になっています。手元で確かめてみてください。
ターミナル
ps -ef | headPPID の列をたどると、ps の親はシェル、シェルの親はログインを受け付けたサービス、と遡っていき、最後は PID 1 に着きます。起動中のすべてのプロセスが、この 1 軒目の子孫です。