システムインタフェース

第 2 章 プロセス

この章では、プロセスとそれらを操作するライブラリ関数を説明します。

概要

コマンドを実行すると、オペレーティングシステムによって番号が付けられ、追跡されるプロセスが開始されます。オペレーティングシステムには、あるプロセスが常に他のプロセスによって生成されるという柔軟な機能があります。たとえば、システムにログインしてシェルから vi(1) などのエディタを使用します。次に、vi(1) からシェルを起動します。その後 ps(1) コマンドを実行すると、次のように表示されます (ps -f コマンドの実行結果を示します)。

UID

PID

PPID

C

STIME

TTY

TIME

COMD

abc

24210

1

0

06:13:14

tty29

0:05

-sh

abc

24631

24210

0

06:59:07

tty29

0:13

vi c2

abc

28441

28358

80

09:17:22

tty29

0:01

ps -f

abc

28358

24631

2

09:15:14

tty29

0:01

sh -i

この例では、ユーザ abc は 4 つのプロセスを起動しています。プロセス ID (PID) と親プロセス ID (PPID) のカラムは、ユーザ abc がログオンしたときに起動されたシェルがプロセス 24210 であり、その親が初期化プロセス (プロセス ID は 1) であることを示しています。プロセス 24210 はプロセス 24631 の親プロセスで、以下も同様です。

プログラムは、その実行状態によっては他の 1 つ以上のプログラムの実行が必要な場合があります。1 つの実行可能プログラムのサイズを大きくするのは好まれないかもしれません。その理由は次のとおりです。

fork(2) 関数と exec(2) 関数を使用すると、新しいプロセス (生成しようとするプロセスのコピー) を生成し、実行中のプログラムの代わりに新しい実行可能プログラムを起動できます。

関数

表 2-1 の関数は、ユーザプロセスの制御に使用します。

表 2-1 プロセス関数

関数名 

目的 

fork(2)

新しいプロセスを生成する。 

exec(2)

execl(2)

execv(2)

execle(2)

execve(2)

execlp(2)

execvp(2)

プログラムを実行する。 

exit(2)

_exit(2)

プロセスを終了する。 

wait(2)

子プロセスが停止または終了するのを待つ。 

dladdr(3DL)

アドレスをシンボルの情報に変換する。 

dlclose(3DL)

共用オブジェクトを閉じる。 

dlerror(3DL)

診断情報を取得する。 

dlopen(3DL)

共用オブジェクトを開く。 

dlsym(3DL)

共用オブジェクト内のシンボルのアドレスを取得する。 

setuid(2)

setgid(2)

ユーザ ID とグループ ID を設定する。 

setpgrp(2)

プロセスグループ ID を設定する。 

chdir(2)

fchdir(2)

作業用ディレクトリを変更する。 

chroot(2)

ルートディレクトリを変更する。 

nice(2)

プロセスの優先順位を変更する。 

getcontext(2)

setcontext(2)

現在のユーザコンテキストを取得または設定する。 

getgroups(2)

setgroups(2)

補助グループアクセスリストの ID を取得または設定する。 

getpid(2)

getpgrp(2)

getppid(2)

getpgid(2)

プロセス ID、プロセスグループ ID、および親プロセス ID を取得する。 

getuid(2)

geteuid(2)

getgid(2)

getegid(2)

実ユーザ ID、実効ユーザ ID、実グループ ID、および実効グループ ID を取得する。 

pause(2)

シグナルを受信するまでプロセスを一時停止する。 

priocntl(2)

プロセススケジューラを制御する。 

setpgid(2)

プロセスグループ ID を設定する。 

setsid(2)

セッション ID を設定する。 

waitid(2)

子プロセスの状態が変化するまで待つ。 

新しいプロセスの生成

fork(2)

fork(2) を呼び出すと、呼び出しプロセスを正確にコピーして新しいプロセスを生成します。この新しいプロセスを子プロセスといい、呼び出し側を親プロセスといいます。子プロセスは、新しい固有のプロセス ID を取得します。fork(2) は、正常終了すると子プロセスに 0 を戻し、親プロセスに子のプロセス ID を戻します。戻り値によって、それが親プロセスか子プロセスかがわかります。

fork(2) 関数または exec(2) 関数によって生成される新しいプロセスは、3 つの標準ファイル stdinstdoutstderr を含めた開いているすべてのファイル記述子を親から継承します。親プロセスが、子プロセスの出力よりも先に表示しなければならない出力をバッファリングしている場合、fork(2) の前にバッファをフラッシュしなければなりません。

次のコード例は、fork(2) および後続のアクションを呼び出します。


pid_t		pid;

 	pid = fork;
 	switch (pid) {
 		case -1:			/* fork が失敗した */
 			perror ("fork");
 			exit (1);
 		case 0:			/* 新しい子プロセスで */
 			printf ("In child, my pid is: %d¥n", getpid(); );
 			do_child_stuff();
 			exit (0);
 		default:			/* 親では、pid は子の PID を持つ */
 			printf ("In parent, my pid is %d, my child is %d¥n", getpid(), pid);
 			break;
 	}

 	/* 親プロセスコード */
 	...

また、親プロセスと子プロセスの両方がストリームから入力を読み込む場合、一方のプロセスが読み込んだ情報を、もう一方のプロセスは読み込むことができません。これは、入力バッファから、あるプロセスに情報が送られると、読み込みポインタが移動してしまうからです。


注 -

従来は、fork(2)exec(2) を使用して別の実行プロセスを起動し、新しいプロセスが終了するまで待つという方法が使われていました。実際には、第 2 のプロセスはサブルーチンを呼び出すために作成されます。サブルーチンを一時的にメモリに常駐させるには、「実行時リンク」で説明するように、dlopen(3DL)dlsym(3DL)dlclose(3DL) を使用する方が効率的です。


exec(2)

exec(2) は、execl(2)execv(2)execle(2)execve(2)execvp(2) を含む関数ファミリ名です。これらの関数はいずれも、呼び出しプロセスを新しいプロセスで置き換えますが、引数のまとめ方と表し方が異なります。たとえば、execl(2) は次のように使用できます。


execl("/usr/bin/prog2", "prog2", progarg1, progarg2, (char (*)0));

exec1(2) 引数リストは、次のとおりです。

/usr/bin/prog2

新しいプロセスファイルのパス名 

prog2

新しいプロセスが argv[0]に取り込む名前

progarg1

progarg2

prog2 への char (*) 型の引数

(char (*)0)

引数の終わりを示す NULL の char ポインタ 

exec(2) が正常に実行されるといかなる時でも、復帰はありません。新しいプロセスが exec(2) を呼び出したプロセスを上書きしてしまうためです。新しいプロセスは、古いプロセスのプロセス ID やその他の属性を引き継ぎます。exec(2) の呼び出しが失敗すると、制御は呼び出しプログラムに戻され、戻り値 -1 が返されます。errno をチェックすれば、失敗した理由がわかります。

実行時リンク

アプリケーションは、実行中に他の共用オブジェクトにバインドして、アドレス空間を拡張できます。このような共用オブジェクトの遅延バインディングには、次のような長所があります。

プロセスのスケジューリング

UNIX システムのスケジューラは、プロセスをいつ実行するかを決定します。このスケジューラは、構成パラメタ、プロセスの動作、およびユーザの要求に基づいてプロセスの優先順位を管理し、これらの優先順位を使用して CPU にプロセスが割り当てられます。

スケジューラ関数を使用すると、特定のプロセスの実行順序と、各プロセスが他のプロセスに CPU を明け渡すまでに CPU を使用できる時間をすべてユーザが制御できます。

デフォルトでは、スケジューラはタイムシェアリング方式を使用します。タイムシェアリング方式では、プロセスの優先順位を動的に調整して、対話型プロセスには適切な応答性能、CPU を多く使用するプロセスには良いスループットを提供します。

スケジューラは、実時間スケジューリング方式も提供します。実時間スケジューリングにより、ユーザはプロセスごとに優先順位を固定できます。固定優先順位とは、システムによって変更されない優先順位のことです。最も優先順位の高い実時間ユーザプロセスは、他のシステムプロセスが実行可能な状態になっても、実行可能になりしだい常に CPU を使用できます。したがって、プログラムでプロセスの正確な実行順序を指定できます。また、実時間プロセスの応答時間がシステムによって保証されるようなプログラムも作成できます。

ほとんどの SunOS 5.0 から 5.8 のバージョンでは、デフォルトのスケジューラの構成で十分に機能するため、実時間プロセスは必要ありません。管理者が構成パラメタを変更したり、ユーザが各自のプロセスの優先順位を変更したりする必要はありません。ただし、タイミングの制約が厳しいプログラムでは、実時間プロセスでなければ制約を満たせないことがあります。

詳細は、priocntl(1)priocntl(2)dispadmin(1M) の各マニュアルページを参照してください。このテーマの詳細な説明は、第 3 章「プロセススケジューラ」を参照してください。

エラー処理

関数が正常に終了しなかった場合は、いくつかの例外を除いて、常に -1 の値がプログラムに戻されます (『man pages section 2: System Calls』で説明している関数では、戻り値が定義されていないものも若干ありますが、これらは例外です)。この場合、プログラムに -1 が戻されるだけでなく、外部宣言されている変数の errno に整数値が設定されます。C プログラムでは、次の文をプログラムに入れておけば errno の値を判定できます。


#include <errno.h>

関数が正常終了すると、errno の値はクリアされません。したがって、この値を検査するのは、関数が -1 を戻した場合だけにしてください。一部の関数は -1 を戻しますが、errno を設定しません。errno が有効な値を持つことが確かである関数については、マニュアルページを参照してください。エラーについては、Intro() を参照してください。

C 言語の perror(3C) 関数を使用すれば、errno の値に対応するエラーメッセージを stderr に出力でき、strerror(3C) 関数を使用すれば、対応する印字可能文字列を取得できます。