表 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) を呼び出すと、呼び出しプロセスを正確にコピーして新しいプロセスを生成します。この新しいプロセスを子プロセスといい、呼び出し側を親プロセスといいます。子プロセスは、新しい固有のプロセス ID を取得します。fork(2) は、正常終了すると子プロセスに 0 を戻し、親プロセスに子のプロセス ID を戻します。戻り値によって、それが親プロセスか子プロセスかがわかります。
fork(2) 関数または exec(2) 関数によって生成される新しいプロセスは、3 つの標準ファイル stdin、stdout、stderr を含めた開いているすべてのファイル記述子を親から継承します。親プロセスが、子プロセスの出力よりも先に表示しなければならない出力をバッファリングしている場合、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) は、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 をチェックすれば、失敗した理由がわかります。
アプリケーションは、実行中に他の共用オブジェクトにバインドして、アドレス空間を拡張できます。このような共用オブジェクトの遅延バインディングには、次のような長所があります。
アプリケーションの初期化中ではなく、必要なときに共用オブジェクトを処理すると、起動時間を大幅に短縮できる。また、ヘルプやデバッグ情報を含むオブジェクトなどのアプリケーションを実行する場合には、共用オブジェクトが不要になることもある。
アプリケーションでは、ネットワークプロトコルなど、必要なサービスだけを多数の異なる共用オブジェクトから選択できる。
実行中にプロセスのアドレス空間に追加された共用オブジェクトを使用後に解放できる。
アプリケーションが追加の共用オブジェクトにアクセスする場合の典型的な例は次のとおりです。
dlopen(3DL) を使用して共用オブジェクトを配置し、実行中のアプリケーションのアドレス空間に追加する。共有オブジェクトの依存関係も、この時点で見つけて追加する。たとえば、次のようになる。
#include <stdio.h> #include <dlfcn.h> main(int argc, char ** argv) { void * handle; ..... if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) { (void) printf("dlopen: %s¥n", dlerror()); exit (1); } ..... |
追加した共用オブジェクト (1 つまたは複数) は再配置され、新しい共用オブジェクト (1 つまたは複数) 内の初期化セクションが呼び出される。
アプリケーションは dlsym(3DL) を使用して、追加された共用オブジェクト (1 つまたは複数) 内でシンボルの位置を見つける。これにより、アプリケーションはこの新しいシンボルで定義されたデータを参照したり、関数を呼び出したりできる。上記の例の続きは次のようになる。
if (((fptr = (int (*)())dlsym(handle, "foo")) == NULL) || ((dptr = (int *)dlsym(handle, "bar")) == NULL)) { (void) printf("dlsym: %s¥n", dlerror()); exit (1); } |
アプリケーションが共用オブジェクト (1 つまたは複数) の使用を終了すると、dlclose(3DL) を使用してアドレス空間が解放される。解放される共用オブジェクト (1 つまたは複数) 内の終了セクションが、この時点で呼び出される。たとえば、次のようになる。
if (dlcose (handle) != 0) { (void) printf("dlclose: %s¥n", dlerror()); exit (1); } |
これらの実行時リンカインタフェースルーチンを使用した結果として発生するエラー状態は、dlerror(3DL) を使用して表示できる。
実行時リンカのサービスは、ヘッダファイル <dlfcn.h> 内で定義されていて、共用ライブラリ libdl.so.1 を介してアプリケーションで利用できます。たとえば、次のようになります。
$ cc -o prog main.c -ldl |
この場合、ファイル main.c は dlopen(3DL) ファミリの任意のルーチンを参照でき、アプリケーション prog は実行時にこれらのルーチンにバインドされます。
アプリケーションが指定する実行時リンクの詳細は、『リンカーとライブラリ』を参照してください。使用方法については、dladdr(3DL), dlclose(3DL)、dlerror(3DL)、dlopen(3DL)、dlsym(3DL) の各マニュアルページを参照してください。
UNIX システムのスケジューラは、プロセスをいつ実行するかを決定します。このスケジューラは、構成パラメタ、プロセスの動作、およびユーザの要求に基づいてプロセスの優先順位を管理し、これらの優先順位を使用して CPU にプロセスが割り当てられます。
スケジューラ関数を使用すると、特定のプロセスの実行順序と、各プロセスが他のプロセスに CPU を明け渡すまでに CPU を使用できる時間をすべてユーザが制御できます。
デフォルトでは、スケジューラはタイムシェアリング方式を使用します。タイムシェアリング方式では、プロセスの優先順位を動的に調整して、対話型プロセスには適切な応答性能、CPU を多く使用するプロセスには良いスループットを提供します。
スケジューラは、実時間スケジューリング方式も提供します。実時間スケジューリングにより、ユーザはプロセスごとに優先順位を固定できます。固定優先順位とは、システムによって変更されない優先順位のことです。最も優先順位の高い実時間ユーザプロセスは、他のシステムプロセスが実行可能な状態になっても、実行可能になりしだい常に CPU を使用できます。したがって、プログラムでプロセスの正確な実行順序を指定できます。また、実時間プロセスの応答時間がシステムによって保証されるようなプログラムも作成できます。
ほとんどの SunOS 5.0 から 5.8 のバージョンでは、デフォルトのスケジューラの構成で十分に機能するため、実時間プロセスは必要ありません。管理者が構成パラメタを変更したり、ユーザが各自のプロセスの優先順位を変更したりする必要はありません。ただし、タイミングの制約が厳しいプログラムでは、実時間プロセスでなければ制約を満たせないことがあります。
詳細は、priocntl(1)、priocntl(2)、dispadmin(1M) の各マニュアルページを参照してください。このテーマの詳細な説明は、第 3 章「プロセススケジューラ」を参照してください。