プロセスとは、処理のことである。情報処理においてプログラムの動作中のインスタンスを意味し、プログラムのコードおよび全ての変数やその他の状態を含む。オペレーティングシステム (OS) によっては、プロセスが複数のスレッドで構成される場合があり、命令を同時並行して実行する[1][2]。
コンピュータプログラムは命令の受動的集合体である。プロセスはそれら命令の実際の実行である。同じプログラムに対応する複数のプロセスが存在しうる。例えば、同じプログラムのインスタンスをいくつか開始することは、複数のプロセスの実行を意味することが多い。
マルチタスクは複数のプロセスがプロセッサ(CPU)や他のシステム資源を共有するための技法である。各CPUはある時点には1つのタスクだけを実行している。しかし、マルチタスクでは各プロセッサに各タスク間で切り替えさせることができ、各タスクの終了を待たずに次々とタスクを実行できる。OSの実装にもよるが、タスク間切り替えは入出力操作の際に行われたり、タスクが明示的に切り替え可能であることを指示したり、ハードウェア割り込みの際に行われたりする。
マルチタスクの典型的形態としてタイムシェアリングがある。タイムシェアリングは、対話型ユーザアプリケーションの素早い応答を可能にする技法である。タイムシェアリングシステムでは、コンテキストスイッチが高速に行われる。そのため、同一プロセッサ上で複数のプロセスが同時に実行されているように見える。複数プロセスを同時に実行することを並行性 (concurrency) と呼ぶ。
セキュリティと信頼性のため、現代のOSは個々のプロセス間での直接の通信ができないようにしており、厳密に統制・制御されたプロセス間通信機能を提供している。
一般に、プロセスは以下のようなリソースから構成される(あるいは所有している):
OSは各プロセスのこれらの情報の大部分をプロセス制御ブロックというデータ構造に保持している。
これらリソースの一部、少なくともプロセッサの状態情報は、プロセスというよりもスレッドに対応している。
オペレーティングシステムはプロセス同士を分離させておいて必要なリソースを割り当て、プロセス間で互いに干渉しあってシステム障害(例えば、デッドロックやスラッシング)を発生したりしないようにする。オペレーティングシステムはプロセス間通信の機構も用意してプロセスが安全に通信し合えるようにしている。
プロセスは、その役割で分類すると、OSの機能を実現するシステムプロセスと、ユーザー権限で実行されるユーザープロセスがある。
プロセスは、そのプログラム部分の性質で分類すると、以下のようになる。
コンピュータ制御ソフトウェアは1960年代初めまでに IBSYS(英語版) のような監視制御ソフトウェアから実行制御ソフトウェアへと発展した。コンピュータは高速化していったが、CPU時間は安価ではなく、しかも完全に使われたわけではない。そのためマルチプログラミングが必要とされ、また可能となった。
マルチプログラミングとは、複数のプログラムを同時に(並行に、同時に並列ということもある)実行することを意味する。当初は単一のプロセッサ上で動作し、少ない資源を共有していた。マルチプログラミングはまた、より幅広い用語であるマルチプロセッシングの基本形態でもある。
プログラムは、プロセッサへの命令列で構成されている。単一のプロセッサは一度に1つの命令しか実行できない。つまり同時に複数のプログラムを実行することは不可能である。プログラムは長い待ち合わせが必要な計算資源(入力など)を必要とすることがあり、時間のかかる操作(プリンターへの出力など)を行うこともある。そういったとき、プロセッサは何もしていない「アイドル」状態になる。プロセッサを常に動作させるため、そういった入出力待ち状態のプログラムの実行を中断し、別のプログラムを起動または再開させる。ユーザーから見れば、複数のプログラムが同時に動作しているように見える。
間もなく「プログラム」の観念は「実行中プログラムとそのコンテキスト」という観念に拡張された。これがプロセスという概念の誕生である。
これは、リエントラントなコードの発明とともに必要になった。
スレッドという概念が生まれるのはその少し後になる。タイムシェアリングシステム、コンピュータネットワーク、マルチプロセッサ、メモリ共有型コンピュータなどが登場し、古い「マルチプログラミング」は真のマルチタスクやマルチプロセッシングにとって代わられ、さらにはマルチスレッドへと進化していったのである。
マルチタスクオペレーティングシステムでは多くのプロセスを並行実行するためにプロセス間でコンテキストスイッチを行うことがある。ただし1つのシングルコアCPUにおいては、ある一時点にひとつのプロセスしか実行できない(ハードウェアマルチスレッディングなどの技術を使っていない場合[注 1])。
主プログラムを1つのプロセスとし、それ以外を並列に動作するプロセス群として独立させ、非同期に動作するサブルーチンとして実装することもある。プロセスはリソースを所有しており、メモリ上のプログラムの「イメージ」はそのようなリソースの1つと言える。マルチプロセッシングシステムでは多くのプロセスが同じリエントラントなプログラムのメモリ上のコピーを共有しているが、プログラムの「イメージ」は個々のプロセスが所有していると言える(多重仮想記憶)。
組み込みシステムのオペレーティングシステムでは、プロセスをタスクと呼ぶことが多い。「プロセス」(あるいはタスク)という用語は「時間を占める何か」であり、対照的に「メモリ」は「空間を占める何か」である[注 2]。また、ジョブがユーザーから見た処理の単位であるのに対し、「プロセス」は、コンピュータ側から見た処理の単位である。
以上の説明はオペレーティングシステムに管理されるプロセスにも、プロセス計算で定義されるプロセスにも当てはまる。
プロセスが中断された状態ならば、ディスクにスワップアウトすることができるが、仮想記憶システムではこれは透過的であって、あるメモリブロックの内容がディスク上にあってメモリ上にないという状態は正常な状態である。動作中プロセスの「未使用」部分もディスクにスワップアウト(ページアウト)することができる。実行中プログラムやそのデータのいかなる部分も、対応するプロセスを実行するにあたって物理メモリ上に存在する必要はない。しかし、プロセスがディスク上のコードやデータを必要としたとき(対応する仮想アドレスにアクセスしようとしたとき)、実行が中断されてページングとして知られている方法でその内容が物理メモリに移動される。
マルチタスクが可能なOSのカーネルは、個々のプロセスの状態を保持する必要がある。この状態の名称は標準化されていないが、どのOSでも似たような機能を持っている[1]。
以下の典型的なプロセス状態は多くのコンピュータシステム上で見られる。これらの状態のほとんどで、プロセスはメインメモリ上に存在する。
プロセスが最初に作成されると、生成(created)状態あるいは新規(new)状態となる。この状態ではプロセスは走行可能状態にされるのを待つ。この状態遷移をさせるのはスケジューラである。多くのシステムではこの遷移が自動的に(即座に)行われるが、リアルタイムオペレーティングシステムでは遅延が生じることがある。リアルタイムシステムで多くのプロセスを走行可能状態にしてしまうと、過飽和とシステムリソースの競合が発生し、プロセスのリアルタイム性を損なってしまう。
走行可能(ready)状態あるいはスケジュール待ち(waiting)状態のプロセスはメインメモリにロードされCPUによる実行を待っている(ディスパッチャか短期スケジューラによってCPU上にコンテキストスイッチされるのを待っている)。任意の時点の走行可能状態のプロセスは非常に多数になる可能性がある。例えば、プロセッサが1個のシステムでは一度にひとつのプロセスしか実行できないので、他のプロセスの多くは実行されるのを待っている状態となる。
走行中(running)状態、実行中(executing)状態、活性(active)状態などと呼ばれる。この状態のプロセスは現にCPU上で実行されている。この状態で割り当てられたタイムスライスを使い切ると走行可能状態に戻される。あるいはプログラムの実行が終了すれば終了状態になるし、何らかのリソースが必要になればブロック状態になるだろう。
スリープ(sleeping)状態ともいう。プロセスはリソース(ファイル、セマフォ、周辺機器など)を確保できないと、プログラム実行を続けられなくなるのでCPUから外されてブロック(blocked)状態となる。プロセスはそのリソースが利用可能となるまでブロック状態のままとなるが、これがデッドロック状態を発生させることもある。ブロック状態のプロセスに対してオペレーティングシステムはリソースが利用可能となったことを知らせる(オペレーティングシステム自体は割り込みによってリソースが利用可能となったことを知る)。オペレーティングシステムはブロックされなくなったプロセスを再び走行可能状態にし、そこから走行中状態にディスパッチされると、プロセスは利用可能となったリソースを使用することになる。
プロセスは走行中状態でプログラムの実行完了によって終了状態となるか、明示的に終了させられることもある。どちらの場合でもプロセスは終了(terminated)状態になる。プロセスがこの状態になってもメモリから消去されない場合、この状態をゾンビ(zombie)状態とも呼ぶ。
UNIX系オペレーティングシステムにおいて、ゾンビプロセス(Zombie Process)は、処理を完了したがプロセステーブル(プロセス制御ブロック相当)が残っていて、終了ステータスを読まれるのを待っているプロセスである[1][3][4]。この用語のメタファーに従えば、ゾンビプロセスは「死んでいる」が、まだ「死神」が到着していないということになる。
プロセスは終了するときに、使用していた全メモリとリソースを解放して他のプロセスが再利用できるようにする。しかし、プロセステーブルのエントリは残される。親プロセスには子プロセスの終了を知らせるために SIGCHLD シグナルが送られる。親プロセスは SIGCHLD シグナルを必ずしも利用する必要はなく、利用するかどうかはプログラミングの都合による。親プロセスが子プロセスをゾンビ・プロセスにせずに「看取る」ためには、子プロセスの終了ステータスを読み取ってゾンビを削除する。一般的にはプログラムの適当な場所で waitpid システムコールを WNOHANG オプションつきで呼び出すか、シグナル・ハンドラで SIGCHLD を捕捉し wait を実行するか、または signal もしくは sigaction システム・コールで SIGCHLD に対して SIG_IGN シグナル・ハンドラを設定する方法が代表的である(SIGCHLD に対し SIG_IGN を設定する方法は POSIX.1-1990 では認められていなかったが、POSIX.1-2001 で認められるようになった)。ゾンビの使用しているプロセス識別子とプロセステーブルエントリがそれによって再利用可能となる。このような処理を行わなければゾンビプロセスは残存し続ける。例えば、親プロセスが他の子プロセスを生成しようとしていて、ゾンビとなっているプロセスと同じプロセス識別子を割り当てられたくないときなどは、ゾンビプロセスを残す意味があるだろう。
waitpid
wait
signal
sigaction
ゾンビプロセスは孤児プロセスと同じではない。孤児プロセスはゾンビ状態にはなっておらず、init プロセスを里親としているので必ず waitが実行される。
init
ゾンビプロセスは UNIX の ps コマンドの STAT カラムに "Z" と表示されることで識別される。ゾンビプロセスは一般に非常に短期間しか存在しないが、親プロセスのプログラムにバグがあるとずっと残存することがある。メモリリークと同様、ゾンビプロセスが多少存在しても問題はないが、高負荷状態でゾンビが増えるとプロセスを生成できないなどの問題を発生することになる。
ゾンビプロセスを消去するには、まず親プロセスに kill コマンドで SIGCHLD シグナルを送ってみる。これで親プロセスがゾンビを刈り取らないなら、次は親プロセスを終了させる。プロセスは親プロセスが終了すると init が新たな親に設定される。init は定期的に waitシステムコールを実行しているので、全てのゾンビプロセスを刈り取ってくれる。
仮想記憶をサポートするシステムでは、上記以外に2つの状態がある。どちらの状態もプロセスは二次記憶装置(ハードディスクなど)に格納される。
ひとつは、スワップアウト(またはサスペンド)されたスケジュール待ち状態である。仮想記憶をサポートするシステムでは、プロセスはスワップアウトされることがあり、中期スケジューラがメインメモリから仮想メモリに移動させる。その後、中期スケジューラによってスワップインされ、通常のスケジュール待ち状態になる。
もうひとつは、スワップアウト(またはサスペンド)されたブロック状態である。ブロックされたプロセスもスワップアウトされることがある。この状態ではプロセスはスワップアウトされた上にブロックされているため、先にスワップインされれば通常のブロック状態になるし、先にリソースが利用可能になればスワップアウトされたスケジュール待ち状態になる。
プロセスが互いに通信することを「プロセス間通信」(IPC) と呼ぶ。プロセスはかなり頻繁に通信を必要とする。例えばシェルのパイプラインでは、第1のプロセスの出力を第2のプロセスに渡す必要があり、その後も同様に出力と入力が連鎖する必要がある。その際、割り込みなどを使わずに構造化された方法を使うことが望ましい。
通信するプロセス群は異なるマシン上で動作していてもよい。その場合、それぞれのマシンのOSは異なるかも知れないので、何らかの調停機能(通信プロトコル)が必要となる。
Lokasi Pengunjung: 3.128.204.131