システムインタフェース

第 3 章 プロセス

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

概要

コマンドを実行すると、オペレーティングシステムによって番号が付けられ、追跡されるプロセスが開始されます。オペレーティングシステムには、あるプロセスが常に他のプロセスによって生成されるという柔軟な機能があります。たとえば、システムにログインしてシェルから vi などのエディタを使用します。次に、vi からシェルを起動します。その後 ps コマンドを実行すると、次のように表示されます (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)」関数を使用すると、新しいプロセス (生成しようとするプロセスのコピー) を生成し、実行中のプログラムの代わりに新しい実行可能プログラムを起動できます。

関数

表 3-1 にリストされる関数は、ユーザプロセスの制御に使用します。

表 3-1 プロセス関数

関数名 

目的 

fork

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

exec

execl

execv

execle

execve

execlp

execvp

プログラムを実行する。 

 

 

 

 

exit

_exit

プロセスを終了する。 

wait

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

dladdr

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

dlclose

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

dlerror

診断情報を取得する。 

dlopen

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

dlsym

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

setuid

setgid

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

setpgrp

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

chdir

fchdir

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

chroot

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

nice

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

getcontext

setcontext

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

getgroups

setgroups

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

getpid

getpgrp

getppid

getpgid

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

 

 

getuid

geteuid

getgid

getegid

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

 

 

pause

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

priocntl

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

setpgid

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

setsid

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

waitid

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

新しいプロセスの生成

fork(2)

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

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

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

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


exec(2)

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

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

execl 引数リストは、次のとおりです。

/usr/bin/prog2

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

prog2

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

progarg1

progarg2

prog2 への char (*) 型の引数

(char (*)0)

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

詳細は、execl(2) のマニュアルページを参照してください。

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

実行時リンク

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

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

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

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

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

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

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

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

エラー処理

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


#include <errno.h>

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

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