システムインタフェース

System V セマフォ

セマフォを使用すると、プロセスはステータス情報を問い合わせたり、変更したりできます。通常、セマフォは共用メモリセグメントなどのシステム資源が利用可能かどうかを監視して制御するために使用されます。セマフォは、個々のユニットまたはセット内の要素として操作できます。

System V IPC セマフォは、大きな配列の中に存在できるため、極めて重量級です。より軽量のセマフォをスレッドライブラリ (semaphore(3THR) のマニュアルページを参照) と POSIX セマフォ (「POSIX セマフォ」を参照) で使用できます。スレッドライブラリセマフォは、マッピングされたメモリ (「メモリ管理インタフェース」を参照) と一緒に使用しなければなりません。

セマフォのセットは、制御構造体と個々のセマフォの配列から成ります。デフォルトでは、25 個までの要素を持つことができます。セマフォのセットは、semget(2) を使用して初期化しなければなりません。セマフォ作成者は semctl(2) を使用して、その所有権またはアクセス権を変更できます。アクセス権を持つプロセスは、semctl(2) を使用して操作を制御できます。

セマフォの操作は、semop(2) 関数によって行います。この関数は、セマフォ操作構造体の配列へのポインタを受け入れます。操作配列内の各構造体は、セマフォに実行する操作についてのデータを持ちます。読み取り権を持つプロセスは、セマフォがゼロ値を持っているかどうかを検査できます。セマフォを増分または減分する操作には、書き込み権が必要です。

要求された操作が失敗すると、どのセマフォも変更されません。IPC_NOWAIT フラグが設定されている場合を除いて、プロセスはブロッキングし、次のいずれかが生じるまでブロッキングされたままです。

セマフォを更新できるのは、一度に 1 つのプロセスだけです。異なるプロセスが同時に要求した場合は、任意の順序で処理されます。操作の配列が semop(2) 呼び出しによって与えられると、配列内のすべての操作が正常に終了できるまで更新されません。

セマフォを排他的に使用しているプロセスが異常終了した後、操作を取り消すかセマフォを解放しなければ、セマフォはメモリ内にロッキングされたままになります。これを防止するため、semop(2)SEM_UNDO 制御フラグにセマフォ操作ごとに、それぞれ undo 構造体を割り当てます。この構造体には、セマフォを以前の状態に戻すために必要な情報があります。プロセスが異常終了すると、undo 構造体内の操作がシステムによって適用されます。このようにすれば、プロセスが異常終了しても、セマフォが整合性のない状態になってしまうことはありません。

プロセスがセマフォによって制御される資源へのアクセスを共用する場合は、SEM_UNDO を有効にしてセマフォに対する操作を行なってはいけません。現在、資源を制御しているプロセスが異常終了すると、その資源は整合性のない状態になったと見なされます。別のプロセスがこの資源を整合性のある状態に復元するためには、そのことを認識できなければなりません。

SEM_UNDO を有効にしてセマフォ操作を実行するときは、取り消し操作を行う呼び出しについても SEM_UNDO を有効にしておかなければなりません。プロセスが正常に実行されると、取り消し操作は undo 構造体に補数値を補って更新します。このため、プロセスが異常終了しない限り、undo 構造体に適用された値は最終的に取り消されて 0 になります。undo 構造体は 0 になると削除されます。

SEM_UNDO を整合性なく使用すると、システムが再起動されるまで割り当てられた undo 構造体が削除されないことがあるので、資源の過剰消費につながります。

セマフォのセットの初期化

semget(2) は、セマフォの初期化またはセマフォへのアクセスを行います。呼び出しが成功すると、セマフォ ID (semid) を戻します。key 引数は、セマフォ ID に関連付けられた値です。nsems 引数は、セマフォ配列内の要素数を指定します。nsems が既存の配列の要素数を超える呼び出しは失敗します。正しい数がわからない場合は、この値を 0 に指定すると正しく実行されます。semflg 引数は、初期状態のアクセス権と作成の制御フラグを指定します。

SEMMNI システム構成オプションは、配列内のセマフォの最大許容数を指定します。SEMMNS オプションは、すべてのセマフォのセットを通じて個々のセマフォの最大数を指定します。セマフォのセット間のフラグメンテーションのため、利用できるすべてのセマフォを割り当てられない場合もあります。

次の例は、semget(2) を示しています。


#include			<sys/types.h>
#include			<sys/ipc.h>
#include			<sys/sem.h>
 ...
 	key_t		key;			/* semget() に渡す key */
 	int		semflg;			/* semget() に渡す semflg */
 	int		nsems;			/* semget() に渡す nsems */
 	int		semid;			/* semget() からの戻り値 */
		...
 	key = ...
 	nsems = ...
 	semflg = ...
 	...
 	if ((semid = semget(key, nsems, semflg)) == -1) {
 		perror("semget: semget failed");
 		exit(1);
 	} else
 		exit(0);
 ...

セマフォの制御

semctl(2) は、セマフォのセットのアクセス権とその他の特性を変更します。semctl(2) は、有効なセマフォ ID を指定して呼び出さなければなりません。semnum 値は、そのインデックスによって配列内のセマフォを選択します。cmd 引数は、次のいずれかの制御フラグです。

GETVAL

単一セマフォの値を戻す。 

SETVAL

単一セマフォの値を設定する。この場合には、arg は intarg.val と解釈される。

GETPID

セマフォまたは配列に対して最後に操作を実行したプロセスの PID を戻す。

GETNCNT

セマフォの値が増加するのを待っているプロセス数を戻す。 

GETZCNT

特定のセマフォの値が 0 に達するのを待っているプロセス数を戻す。 

GETALL

セット内のすべてのセマフォの値を戻す。この場合には、arg は unsigned short の配列へのポインタ arg.array と解釈される。

SETALL

セットにあるすべてのセマフォに値を設定する。この場合、arg は unsigned short の配列へのポインタ arg.array と解釈される。

IPC_STAT

制御構造体からセマフォのセットの状態情報を取得し、それを semid_ds 型のバッファへのポインタ arg.buf が指すデータ構造体に入れる。

IPC_SET

有効なユーザおよびグループの識別子とアクセス権を設定する。この場合、arg は arg.buf と解釈される。

IPC_RMID

指定したセマフォのセットを削除する。 

IPC_SET または IPC_RMID コマンドを実行するには、所有者、作成者、またはスーパーユーザとして有効なユーザ識別子を持っていなければなりません。その他の制御コマンドには、読み取り権と書き込み権が必要です。

次に、semctl(2) の使用例を示します。


#include				<sys/types.h>
#include				<sys/ipc.h>
#include				<sys/sem.h>
 ...
 	register int				i;
 ...
 	i = semctl(semid, semnum, cmd, arg);
 	if (i == -1) {
 		perror("semctl: semctl failed");
 		exit(1);
 ...

セマフォの操作

semop(2) は、セマフォのセットへの操作を実行します。semid 引数は、前の semget(2) 呼び出しによって戻されたセマフォ ID です。sops 引数は、セマフォ操作について次のような情報を含んでいる構造体の配列へのポインタです。

sembuf 構造体は、<sys/sem.h> に定義されているセマフォ操作を指定します。nsops 引数は配列の長さを指定します。配列の最大長は、SEMOPM 構成オプションで指定されます。これは単一の semop(2) 呼び出しで許される最大操作数で、デフォルトでは 10 に設定されています。

実行する操作は、次のように判定されます。

semop(2) では、次の 2 つの制御フラグを使用できます。

IPC_NOWAIT

配列内のどの操作についても設定できる。この関数は、IPC_NOWAIT が設定されている操作が正しく実行できなかった場合に、セマフォの値を変更せずに戻る。セマフォを現在の値より多く減らそうしたり、セマフォがゼロでないときにゼロかどうか検査しようとすると関数は失敗する。

SEM_UNDO

プロセスの終了時に配列内の個々の操作を取り消す。 

次に、semop(2) 関数の使用例を示します。


#include				<sys/types.h>
#include				<sys/ipc.h>
#include				<sys/sem.h>
 ...
 	int				i;			/* 作業領域 */
 	int				nsops;			/* 実行する操作数 */
 	int				semid;			/* セマフォのセットの ID */
 	struct sembuf				*sops;			/* 実行する操作へのポインタ */
 	...
 	if ((i = semop(semid, sops, nsops)) == -1) {
 		perror("semop: semop failed");
 	} else
 		(void) fprintf(stderr, "semop: returned %d¥n", i);
 ...