プロセスの一生

このエントリーをはてなブックマークに追加

プロセス

プロセスはOSが備える機能の最も大事な機能の1つです。これにより、処理が複数に分けられて、またお互いに不干渉(特にメモリが)になります。
マルチプロセス処理はUnixではよく使われる機能です。またこの応用としてプロセスのデーモン化があります。
ここではプロセスの生成、終了の方法を簡単に紹介します。詳しくは次の章以降で解説を行います。
また、注意事項としてスレッドに関しては触れませんのでご注意ください。

プロセスの概念

Unixにおけるプロセス

Unixではfor(2)を用いてプロセスを作成します。
fork(2)は現在のプロセスをコピーして作成されます。
例えばコマンドラインからなにかコマンドを起動するとshellがfork(2)を行うのです。
また、パイプでつなぐフィルタ動作はこのマルチプロセスが前提となっており、これにより小さなプログラムを複数つなぐことにより複雑な事を実現するUnixの哲学の基礎を支えているのです。


プロセスの作成

main関数

Cのプロセスはmain関数から起動されます。
main関数のプロトタイプは次の通りです。

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

ちなみにvoid mainがC99からOKになったりと多少の変則はあります。


main関数は誰が呼び出す?

通常は気にしませんが、main関数を呼ぶのはshellなどから直接呼ばれるのではありません。
この前に、特別な処理が存在しており実行可能なプログラムを作成する時にコンパイラが裏でリンクしているのです。
この特別な処理部分は、カーネルから様々な値を受け取り、argc、argvを作成しmain関数を呼び出すのです。
もしこういった低レベル部分に興味がある方は、gccであればcrt1.oをキーワドに検索すれば良いでしょう。


プロセスの終了

プロセスの終了にはいくつかの方法がありますので、紹介していきます。


main関数から戻る

システム周りで取り上げていますが、mainの戻り値が呼び出し側に返されプログラムの終了状態を知ることが可能です。
通常の終了の場合は、return文を使用します。
戻り値には整数を使用します。

int main(int argc, char *argv[])
{
	//正常終了
	return 0;

	//異常終了
	return 1;
}

さて余談ですが、C99からmainの型にvoidが許可されています。ただし環境依存となりますが。
環境依存ではありますがvoid mainの返り値がどうなるか見てみましょう。
void main{
}

$ gcc -o test test.c
$ ./test
$ echo $?
237

Linux上では237という値が返るようです。ちなみにMacでは144という値になっています。
これらは固定の値ですので、おそらくcrt1.o等で固定値を返しているのでしょうね。


exit関数群

returnで戻る以外にもexit関数群を使用する事により、その場でプログラムを終了させることが可能です。
これはプログラムの途中で異常終了したいなどの場合によく使用されます。
exit関数群のプロトタイプです。

#include <stdlib.h>

void exit(int status);
void _Exit(int status);

#include <unistd.h>
void _exit(int status);

exitの使い方は簡単です。
#include <stdlib.h>

int main()
{

	//正常終了
	exit(0);
	exit(EXIT_SUCCESS)

	//異常終了
	exit(1);
	exit(EXIT_FAILURE)
}

exitの動作を簡単に説明するのであれば、呼ばれた時点で後始末を行ってから、プロセスの終了を行います。
例えば、exitが呼ばれるとopenされているファイルディスクリプタを全てcloseしフラッシュされます。
また、tmpfileによって作成されたファイルは削除されます。
ですので、ファイルやストリームへのcloseに関しては、タイミングを気にしなければ気を使い過ぎ無くても問題はありません(あくまでも規格上はですが)。

しかし、mallocされているメモリは規格上はfreeされることは保証していません。最近の通常の環境であればほぼfreeされますが一応知識としては抑えておくべきところでしょう。
組み込みや非常に古いOS等の特殊環境の場合はきちんとfreeの処理を行うべきでしょう。

またatexit、on_exit関数を使用する事により、終了前に呼び出してほしい関数を登録することが可能です。

manを見ると、atexit等にも言及されていますので是非一度は目を通しておいてください。


_exit

_exit、_Exit関数は基本的にはexit関数と同じです。ただし、終了処理が若干異なります。
例えば、atexit、on_exit関数を呼ばない等いくつかの動作が異なります。
_exit関数は主にfork、vfork関数の終了処理に使用します。


異常終了

returnやexit等での終了は正常終了ですが、プロセス自体は外部からの異常終了が存在します。


abort

abortは異常終了処理を行います。
exit(1)は終了処理を行いmain関数の返り値を1にするだけですが、abortは異なりSIGABRTを発生させます。
また、当然全てのストリームはcloseされます。
この動作はforkを意識したものです。シグナルについてはこちらを参照してください。
また、使用方法等についてはforkにとともに別途解説を致します。


外部からのシグナル

exit、abort等はプロセスが自分自身で終了を行う処理でしたが、シグナルを使用することにより外部から対象プロセスを終了することが可能となります。
また、メモリ破壊などの要因でOSから終了させられる場合もあります。

簡単にシグナルを発生させるにはシグナルの概念にあるように、killコマンドを使用します。


まとめ

ここではプロセスの生成から終了までの流れを軽く見てきました。
次ではforkを中心としたプロセスの生成方法等を学びます。