プロセスについて調べたことをまとめました


プロセスとは?

プロセスとは一言で言えば、メモリ上で実行されているプログラムのこと。

initプロセス

一番最初に起動されるプロセスで、デーモン・プログラムとして動作する。他のプロセスを起動する役目を負う全てのプロセスの始祖。すべてのプロセスは、その親をたどっていくと、initプロセスに行き着く。initはカーネルがブート直後に起動し、システムシャットダウン時まで決して終了しない。

Macでは/sbin/launchdとして、Linuxでは/sbin/initとして起動される。

initプロセスのプロセスIDは必ず1となり、プロセスIDはps axコマンドで確認できます。

❯ ps ax | head -2
  PID   TT  STAT      TIME COMMAND
    1   ??  Ss     3:29.80 /sbin/launchd

親プロセス

別のプロセスを作ったプロセスを親プロセスと呼ぶ。fork()すると子プロセスを作ることができる。どのようなときに子プロセスを作るかというと、主に

  • 新しいプログラムを実行したいとき
  • 並列に処理を行いたいとき
  • デーモンプロセスを作成したいとき

などに利用される。Apacheの子プロセスの使用例(後述)が分かりやすい。

https://kaworu.jpn.org/c/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%81%AE%E4%BD%9C%E6%88%90_fork より

子プロセスを起動すると、その子プロセスの内容はPIDを除いて基本的に親プロセスと同一となる。

子プロセス

子プロセスとは、fork()して他のプロセスから作られたプロセスのことを言う。

子プロセスはコピーオンライト

子プロセスを立ち上げるためにforkした時点では、子プロセスに専用の仮想アドレス空間が与えられるだけで、実際は親子で共通の物理メモリを共有している状態となる。
これはメモリの使用を最適化するためのもので、親子いずれのメモリ領域にデータの書き込みがなかった場合、子プロセスのために確保したメモリ領域が親プロセスと重複し、無駄になってしまう(既にある親プロセスを参照すれば済むため)。よって、子プロセスのメモリ領域の確保は、コピーオンライトといって親か子、どちらかのプロセスでメモリ内のデータの変更があるまでは物理メモリの領域を確保しないという方式が取られる。
また、たとえ書き込みがあったとしても、書き込みが行われていない領域は共有され続ける。

プロセスIDと、親プロセスIDを見る

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Printf("プロセスID: %d\n", os.Getpid())
    fmt.Printf("親プロセスID: %d\n", os.Getppid())
}

プロセスの関数

fork()

呼び出し元のプロセスをコピーして新たなプロセスを生成する。呼び出し元のプロセスが親プロセスとなり、生成されたプロセスは呼び出し元の子プロセスとなる。子プロセスは基本的に親プロセスの複製だが、プロセスIDは親プロセスとは異なるのに加え、メモリロックやセマフォの値などは共有されない。
fork()の返却値は以下のとおり。

返却値 値の意味
0 fork()成功、fork()された子プロセス側では0を返す
0以上 fork()成功、fork()された子プロセスのpid
-1 fork()失敗

ソースコード上では、この返却値を使って子プロセスなのか親プロセスなのかを判別し、処理を行う。

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>  // fork
#include <unistd.h>     // fork

#include <err.h>
#include <errno.h>

int
main(int argc, char *argv[])
{
        pid_t   pid;

        pid = fork ();

        if (-1 == pid)
        {
                err (EXIT_FAILURE, "can not fork");
        }
        else if (0 == pid)
        {
                // child
                puts ("child");
                exit(EXIT_SUCCESS);
        }
        else
        {
                // parent
                puts ("parents");
        }

        exit (EXIT_SUCCESS);
}

https://kaworu.jpn.org/c/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%81%AE%E4%BD%9C%E6%88%90_fork より引用

親子のプロセスが作られるときは、どちらかのプロセスでメモリを変更するまではメモリの実体をコピーしない「コピーオンライト」でメモリが共有される。そのため、子プロセス生成時に瞬時にメモリ消費量が大きく増えることはない。fork()が失敗するケースの多くはOSのリソースが枯渇していることによる。

exec()

exec()を使うと、プロセス識別子 (PID) は変化しないが、プロセスの仮想空間上のスタック、ヒープ、データなどは全て新たなものに置換される。fork()と組み合わせる事で親プロセスの複製ではない、新しいデータを持つプロセスを新規に作る事が出来る。

wait(), waitpid()

親プロセスが子プロセスの状態を取得するのに使用される関数で、終了ステータスを返す。この終了ステータスを元に親プロセスは子プロセスが終了したことを確認することによって、プロセステーブルから子プロセスが適切に削除される。何らかの原因で子プロセスが終了したにもかかわらず親プロセスにwait()されなかった子プロセスはゾンビプロセスになる。

ゾンビプロセス

既に終了しているのに、まだプロセステーブルに残っているプロセスのこと。親プロセスがwait()した時に、プロセステーブルからプロセスの情報が消えるが、なんらかの理由でwait()されなかった場合、ゾンビプロセスとなる。ゾンビプロセスとなったプロセスは最終的にはinitプロセスの子プロセスとなり、initプロセスがwait()を実行した時に、プロセステーブルから情報が消される。

プロセスのアドレス空間

ここで、後述するといったプロセス一つ一つに割り当てられる仮想アドレス空間の内部構造について説明します。
これがプログラムがメモリにロードされた時のアドレス空間になります。スレッドは図中のテキストセグメントと呼ばれる部分にあります。上が0番地、下がFFFFFFFF番地です。

データセグメント

データセグメントはデータを格納する領域で、さらに3つの領域に分けられる。

  1. 定数領域
  2. 静的変数領域
  3. ヒープ

定数領域、静的変数領域はそのままなので説明な特に不要かと思います。ヒープは、プログラムの実行中に動的に確保される領域です。例えばC言語のmalloc関数やJavaなどの言語ではnewでインスタンス化したものがここに収められます。不要になったオブジェクトは言語によってはdelete関数で削除されたり、ガーベージコレクションがある言語ではそれによって適切に廃棄され、ヒープを解放して枯渇するのを防ぎます。

テキストセグメント

CPUは機械語で書かれたプログラムをメモリから読み込んで実行していく。スレッドはCPUに実行される命令の列。テキストセグメントはプログラムがロードされる領域。すなわちテキストセグメントは命令の列、スレッドが格納される領域である。図にあるように、スレッドはアドレス空間を共有することができ、その分スレッドはプロセスよりもスケジュールのコストが低い。

スタックセグメント

  • テキストセグメントと違い、スタックセグメントはスレッドの数だけ用意される(複数スレッドで共有することができない)
  • プログラムの実行時に、一時的に記憶しておくデータを格納するために動的に使用される(引数やリターンアドレスなど)
  • FILO(First In Last Out)方式でデータの格納と取り出しが行われる。

プロセステーブル

システム内では、プロセスはデータ構造として表される。プロセスのデータ構造がプロセス制御ブロックで、プロセステーブルとは存在するプロセスのプロセス制御ブロックを列に持つテーブル。両者を同じものとして説明しているサイトもあるが、区別していないサイトもある。ここでは区別する。

画像はhttp://www.technologyuk.net/computing/computer-software/operating-systems/process-management.shtml から引用

プロセステーブルとは、動いているプロセスの情報が格納されているデータ構造体で、メモリ上に存在する。以下のような情報を持っている。

属性 意味
プロセスID プロセスの識別に利用される
プログラムカウンター プロセス内で次に実行する命令のアドレス
レジスタ
プロセスの状態
スケジューリング情報 スケジューリング(プロセスの優先度を決める)に使用
タイムスライス この時間を経過した場合、待ちプロセスの処理に切り替わる
プロセスの所有者
プロセスの仮想アドレス空間 メモリ上でのアドレス
各プロセスの環境変数
親プロセス 親プロセスのID
開かれたファイル プロセスの実行中に開かれたファイルのリストの情報など
プロセスの実行時間 最後にプロセスが実行された時間(スケジューリングで使用する)

プロセスが終了すると、ここから情報が削除される。削除されない場合はゾンビプロセスになる。

プロセス制御ブロック(Process Control Block)

コンテキストスイッチのとき、現在実行中のプロセスは一時停止し、他のプロセスが実行される。カーネルは実行中のプロセスを停止し、そのときのレジスタの値をPCBにセーブし、新たに動作させるプロセスのPCBからレジスタに値をリストアする。

ユーザープロセス

マルチタスクOSでは複数のプロセスを同時に実行できるため、OSの機能の一部を、独立したプロセスとして実行することも可能。これをシステムプロセスと呼ぶ。それに対して、ユーザーが起動したプロセスがユーザープロセスという。ユーザープロセスは一般に権限が制限されていて、システムプロセスに比べ、アクセスできるリソースが制限されている。

システムプロセス

マルチタスクOSにおいて、OSの機能を実現するためのプロセス。所有者はroot。カーネルもシステムプロセスになる。

プロセスグループ

プロセスグループ (Process Group) とは、POSIX準拠のオペレーティングシステムにおいて、1個以上のプロセスの集まりを意味する。この集まりはプロセスグループリーダーとなっているプロセスのプロセスIDと同じ値をプロセスグループIDとして識別に使用する。

プロセスグループはシグナルを複数のプロセスに配布するために使用される。killシステムコールはシグナルを個々のプロセスに送るだけでなく、プロセスグループに送ることもできる。プロセスグループに向けられたシグナルは、そのグループのメンバーである全プロセスに送られる。

「シグナル」はプロセスとプロセスの間で通信を行う際に使用される“信号”のことで、シグナルを受け取ったプロセスは“何らかの動作”を行います。その動作は、例えば「再起動」であったり、「終了」であったりします。

https://www.atmarkit.co.jp/ait/articles/1708/04/news015.html より

フォアグラウンドプロセス

キーボードから送るシグナルのように、端末からの入力を受け付ける状態になっているプロセスグループのことをフォアグラウンドプロセスグループといい、そうでない他の全てのプロセスグループのことをバックグラウンドプロセスグループという。グループをつけず、単にフォアグラウンドプロセス、バックグラウンドプロセスと呼ぶことも。

デーモンプロセス

デーモンは,LinuxやUNIXにおいてメモリ上に常駐して様々なサービスを提供するプロセスを指す。デーモンプロセスは処理要求を待ち続け,要求があると自分自身をコピーしたプロセスを作り,コピーしたプロセスに処理を実行させる。

Webサーバなどのように,同時にたくさんの処理を行わなくてはならない場合,クライアントから要求があってから子プロセスを生成すると対応に時間がかかってしまう。
そこでWebサーバはあらかじめクライアントから要求がくる前に,複数の子プロセスを生成しておく。クライアントから接続要求がくると,事前に生成しておいた子プロセスに処理を行わせる。事前に起動しておくことで,サーバ側での処理を早くすることができる。
デーモンは必要に応じてプロセスを生成するので,要求が多い場合にはその数だけデーモンの子プロセスが必要になる。しかし,プロセスの数が増えると,Linuxカーネルがそれらのプロセスの切り替えに手間取るようになるため,逆に処理速度が低下してしまう。子プロセスを事前にいくつ生成するか設定を確認する必要がある。

Apacheの例

例えば、Apacheでは制御を担当する親プロセスが一つあり、実際にWebサーバとして機能するのは自らをforkして生み出した子プロセスで、その子プロセスにリクエストを処理させる。この子プロセスがApacheのデーモン、httpdである(httpdだからと言って必ずしもApacheのデーモンだとは限らない)。

このように子プロセスを複数用意してそれぞれに処理を行わせることで、Webサーバが複数のリクエスを同時に受け付けた場合でも、並列に処理をすることができる。

プロセスの状態

実行状態/Running

CPUがプロセスを実行している状態のこと。実行可能状態にあるプロセスは、CPUに空きができた時点で実行状態に移る(ディスパッチ)。ただし、一度に実行状態に移ることができるのは一つのプロセスだけ。

実行可能状態/Waiting

プロセスを実行することができるが、CPUは他のプロセスを実行しているためCPUが割り当てられるのを待っている状態のこと。強制的にプロセスを切り替えることをプリエンプションといい、プリエンプティブなプロセスの切り替えは特定のプロセスの実行時間が長くなりすぎた時に行われる。
例えば、UNIX系のOSでは、プロセスの最大実行時間を0.1秒程度に設定されている。このプロセスの最大実行時間のことをタイムクォオンタムと呼び、時間が切れたら次に待っているプロセスに順番にCPUを割り当てる。順番にプロセスを切り替えることをラウンドロビンプロセス切り替えという。

待機状態/Blocked

入出力処理など時間がかかる処理を待っている状態で、その処理が終わらないとプログラムの実行を再開できない状態のプロセス。
実行中に入出力処理等(例えばキーボードやマウスからの入力)が始まったときは、優先的にそちらを処理して、それまで実行状態にあったプロセスは待ち状態に移る(割り込み)。待ち状態にあるプロセスは、入出力処理等が完了して実行できる状態になったときに、実行可能状態に移る。

スワップされた実行可能状態

プロセスはメインメモリからストレージにスワップされることがある。実行可能状態のプロセスがスワップされている状態のこと。スワップされている実行可能状態から、実行可能状態へと戻ることができる。

スワップされた待機状態

スワップされた実行可能状態と同じで、待機状態のプロセスがスワップされた場合メモリから除かれ、スワップされた待機状態になる。

生成

プロセスが最初に作成されると、生成(created)状態あるいは新規(new)状態となる。この状態ではプロセスは実行可能状態にされるのを待つ。この状態遷移をさせるのはスケジューラである。

プロセス間通信

既に詳しく説明している記事があったので、こちらを参考にしてください。

アクティブティモニターでプロセスを見てみる

システムプロセス

View > System Processes でシステムプロセスを見ることができます。プロセスの所有者はrootになっています。karnel_taskというプロセスがあります。カーネルにはあらゆる役目がありますが、カーネルの動きをkarnel_taskとして表しています。

ユーザプロセス

My Processesにすると、開いているアプリが出てきます。所有者は全てMac OSのユーザになっているはずです。

その他

Swap Usedがスワップされたプロセスになります。
ちなみに、AppleのサポートページによるとlCached Filesは最近あるアプリによって使われていたメモリ領域で現在他のアプリのために利用可能なメモリのこと。開いていたアプリを閉じた時、それまでそのアプリが使っていたメモリ領域は解放されず、Cached Filesに含まれます。そのCached Filesのメモリ領域が他のアプリによって利用され上書きされる前に同じアプリを再び開いた場合、起動ディスクから読み込むよりも早くアプリを開くことができます。

psコマンドでプロセスを見てみる

psコマンドで表示される項目の意味は、以下のページが詳しいです。
https://eng-entrance.com/linux-process

感想

プロセスについてよく分かっていないということが判明したので、調べたことをまとめてみました。
ささっとまとまるかと思ったのですが、調べてみると思ったよりずっと奥が深そうで、かなり長くなってしまう気がしましたが書き終わって読み返してみればまとめるのに時間がかかった割にはそうでもありませんでした。
アウトプット学習としてささっとまとめるという趣旨から始まり、途中からプロセスについてできるだけ深堀りするものに変更し、プロセスについてどこまで知ってる?というタイトルで投稿しようと思っていましたが、若干挑戦的な響きがあるのに加え書き終わってみればタイトルに見合わない内容となったため変更しました。それでも、前より知識が増えたのでよかったです。

最後までお読みいただきありがとうございました。

参考

https://eng-entrance.com/linux-process
https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97
https://qiita.com/tajima_taso/items/c5553762af5e1a599fe
https://www.geeksforgeeks.org/process-table-and-process-control-block-pcb/
http://uralowl.my.coocan.jp/unix/job/UNIX/kernel/process.html
https://stackoverflow.com/questions/4880555/what-is-the-linux-process-table-what-does-it-consist-of/47973661#47973661
https://pubs.opengroup.org/onlinepubs/7908799/xsh/wait.html
https://blog.neet-shikakugets.com/archives/1003
https://support.apple.com/en-us/HT201464