プロセスは、プログラムの実行中のインスタンスに他なりません。また、実行中のプログラムとしても定義されます。
プロセスの概念は、Linux システムの基本的な概念です。プロセスは、他のプロセスを生成したり、他のプロセスを強制終了したり、他のプロセスと通信したりできます。
このチュートリアルでは、プロセスのライフ サイクルについて説明し、ライフ サイクルでプロセスが通過するさまざまな側面に触れます。
1.コード対プログラム対プロセス
まずコード、プログラム、プロセスの違いを理解しましょう。
コード: 以下はコードの例です:
#include <stdio.h> #include <unistd.h> int main(void) { printf("\n Hello World\n"); sleep(10); return 0; }
上記のコードを helloWorld.c という名前のファイルに保存しましょう。したがって、このファイルはコードになります。
プログラム: コードがコンパイルされると、実行可能ファイルが生成されます。上記のコードをコンパイルする方法は次のとおりです:
$ gcc -Wall helloWorld.c -o helloWorld
これにより、helloWorld という名前の実行可能ファイルが生成されます。この実行可能ファイルはプログラムと呼ばれます。
プロセス: それでは、この実行可能ファイルを実行してみましょう:
$ ./helloWorld Hello World
実行すると、この実行可能ファイル (またはプログラム) に対応するプロセスが作成されます。このプロセスは、プログラム内にあったすべてのマシン コードを実行します。これが、プロセスがプログラムの実行中のインスタンスとして知られる理由です。
新しく作成されたプロセスの詳細を確認するには、次の方法で ps コマンドを実行します:
$ ps -aef | grep hello* 1000 6163 3017 0 18:15 pts/0 00:00:00 ./helloWorld
ps コマンドの出力を理解するには、7 ps コマンドの例に関する記事をお読みください。
2.親と子のプロセス
すべてのプロセスには親プロセスがあり、子プロセスがある場合とない場合があります。これを一つ一つ見ていきましょう。私の Ubuntu マシンでの ps コマンドの出力を考えてみましょう:
1000 3008 1 0 12:50 ? 00:00:23 gnome-terminal 1000 3016 3008 0 12:50 ? 00:00:00 gnome-pty-helper 1000 3017 3008 0 12:50 pts/0 00:00:00 bash 1000 3079 3008 0 12:58 pts/1 00:00:00 bash 1000 3321 1 0 14:29 ? 00:00:12 gedit root 5143 2 0 17:20 ? 00:00:04 [kworker/1:1] root 5600 2 0 17:39 ? 00:00:00 [migration/1] root 5642 2 0 17:39 ? 00:00:00 [kworker/u:69] root 5643 2 0 17:39 ? 00:00:00 [kworker/u:70] root 5677 2 0 17:39 ? 00:00:00 [kworker/0:2] root 5680 2 0 17:39 ? 00:00:00 [hci0] root 5956 916 0 17:39 ? 00:00:00 /sbin/dhclient -d -sf /usr/lib/NetworkManager/nm-dhcp-client.action -pf /run/sendsigs. root 6181 2 0 18:35 ? 00:00:00 [kworker/1:0] root 6190 2 0 18:40 ? 00:00:00 [kworker/1:2] 1000 6191 3079 0 18:43 pts/1 00:00:00 ps -aef
上記の出力の 2 列目と 3 列目の整数は、プロセス ID と親プロセス ID を表します。太字で強調表示されている図に注意してください。コマンド「ps -aef」を実行すると、プロセスが作成されました。そのプロセス ID は 6191 です。次に、その親プロセス ID を見てください。3079 です。出力の最初の方を見ると、ID 3079 が表示されます。 bashプロセスのプロセスIDです。これにより、実行するすべてのコマンドの親が bash シェルであることが確認されます。
同様に、シェルを介して作成されていないプロセスの場合でも、親プロセスがいくつかあります。 Linux マシンで「ps -aef」コマンドを実行し、PPID (親プロセス ID) 列を確認するだけです。空のエントリは表示されません。これにより、すべてのプロセスに親プロセスがあることが確認されます。
さて、子プロセスに行きましょう。プロセスが別のプロセスを作成するたびに、前者は親プロセスと呼ばれ、後者は子プロセスと呼ばれます。技術的には、コード内から fork() 関数を呼び出すことによって、子プロセスが作成されます。通常、シェルからコマンドを実行すると、fork() の後に exec() 一連の関数が続きます。
すべてのプロセスには親プロセスがあると説明しましたが、これは、親プロセスが強制終了された子プロセスに何が起こるかという疑問をもたらす可能性があります。これは良い質問ですが、後でもう一度考えてみましょう。
3.初期化プロセス
Linux システムが起動すると、最初にメモリに読み込まれるのは vmlinuz です。これは、圧縮された Linux カーネル実行可能ファイルです。これにより、init プロセスが作成されます。これは作成される最初のプロセスです。 init プロセスの PID は 1 で、Linux セッション内のすべてのプロセスのスーパー ペアレントです。 Linux プロセス構造をツリーと見なす場合、init はそのツリーの開始ノードです。
init が最初のプロセスであることを確認するには、Linux ボックスで pstree コマンドを実行します。このコマンドは、Linux セッションのプロセスのツリーを表示します。
出力例は次のとおりです:
init-+-NetworkManager-+-dhclient | |-dnsmasq | `-3*[{NetworkManager}] |-accounts-daemon---2*[{accounts-daemon}] |-acpid |-at-spi-bus-laun-+-dbus-daemon | `-3*[{at-spi-bus-laun}] |-at-spi2-registr---{at-spi2-registr} |-avahi-daemon---avahi-daemon |-bamfdaemon---3*[{bamfdaemon}] |-bluetoothd |-colord---{colord} |-console-kit-dae---64*[{console-kit-dae}] |-cron |-cups-browsed |-cupsd |-2*[dbus-daemon] |-dbus-launch |-dconf-service---2*[{dconf-service}] |-evince---3*[{evince}] |-evinced---{evinced} |-evolution-sourc---2*[{evolution-sourc}] |-firefox-+-plugin-containe---16*[{plugin-containe}] | `-36*[{firefox}] |-gconfd-2 |-gedit---3*[{gedit}] |-6*[getty] |-gnome-keyring-d---7*[{gnome-keyring-d}] |-gnome-terminal-+-bash | |-bash-+-less | | `-pstree | |-gnome-pty-helpe | `-3*[{gnome-terminal}] |-gvfs-afc-volume---2*[{gvfs-afc-volume}] |-gvfs-gphoto2-vo---{gvfs-gphoto2-vo} |-gvfs-mtp-volume---{gvfs-mtp-volume} |-gvfs-udisks2-vo---{gvfs-udisks2-vo} |-gvfsd---{gvfsd} |-gvfsd-burn---2*[{gvfsd-burn}] |-gvfsd-fuse---4*[{gvfsd-fuse}] ... ... ...
出力は、init がプロセス ツリーの一番上にあることを確認します。また、太字のテキストを観察すると、pstree プロセスの完全な親子関係がわかります。 pstree の詳細については、tree と pstree に関する記事をご覧ください。
ここで、子がまだ生きている間に親プロセスが強制終了された場合の結果についての質問 (前のセクションで開いたままにしました) に戻りましょう。この場合、子は明らかに孤児になりますが、init プロセスによって採用されます。そのため、init プロセスは、親が終了した子プロセスの新しい親になります。
4.プロセスのライフサイクル
このセクションでは、通常の Linux プロセスが強制終了され、カーネル プロセス テーブルから削除されるまでのライフ サイクルについて説明します。
- すでに説明したように、新しいプロセスは fork() によって作成され、新しい実行可能ファイルを実行する場合は、fork() の後に exec() ファミリーの関数が呼び出されます。この新しいプロセスが作成されるとすぐに、実行準備が整ったプロセスのキューに入れられます。
- fork() のみが呼び出された場合、新しいプロセスはユーザー モードで実行される可能性が高くなりますが、exec() が呼び出された場合、新しいプロセス アドレス空間が作成されるまで、新しいプロセスはカーネル モードで実行されます。
- プロセスの実行中に、優先度の高いプロセスが割り込みによってそのプロセスを横取りできます。この場合、横取りされたプロセスは、実行準備ができているプロセスのキューに再び入れられます。このプロセスは、後の段階でスケジューラによって取り上げられます。
- プロセスは、実行中にカーネル モードに入ることができます。これは、ハードディスクに保存されているテキスト ファイルなどのリソースにアクセスする必要がある場合に可能です。ハードウェアへのアクセスを伴う操作には時間がかかる可能性があるため、プロセスがスリープ状態になり、要求されたデータが利用可能になったときにのみウェイクアップする可能性が高くなります。プロセスが起動されても、すぐに実行が開始されるわけではありません。プロセスは再びキューに入れられ、適切なタイミングでスケジューラによって実行のために選択されます。
- プロセスはさまざまな方法で強制終了できます。 exit() 関数を呼び出して終了するか、Linux シグナルを処理して終了することができます。また、一部のシグナルはキャッチできず、プロセスがすぐに終了します。
- Linux プロセスにはさまざまな種類があります。プロセスが強制終了されると、完全には削除されません。関連する情報を含むエントリは、親プロセスが明示的に wait() または waitpid() 関数を呼び出して子プロセスの終了ステータスを取得するまで、カーネル プロセス アドレス テーブルに保持されます。親プロセスがこれを行うまで、終了したプロセスはゾンビ プロセスと呼ばれます。