システムインタフェース

System V IPC

Solaris 8 およびその互換オペレーティング環境では、パイプや名前付きパイプよりも多目的に使用できる 3 種類のプロセス間通信をサポートしているプロセス間通信 (IPC) パッケージが提供されています。

System V IPC の詳細は、 ipcrm(1)ipcs(1)Intro(2)msgctl(2)msgget(2)msgrcv(2)msgsnd(2)semget(2)semctl(2)semop(2)shmget(2)shmctl(2)shmop(2)、 および ftok(3C) のマニュアルページを参照してください。

アクセス権

メッセージ、セマフォ、および共用メモリは、通常ファイルと同じように、所有者、グループ、およびその他のための読み取り権と書き込み権を持っています (実行権は持っていません)。ファイルと同様に、作成元プロセスはデフォルトの所有者を識別します。ファイルとは異なり、作成者は機能の所有権を別のユーザに割り当てることができます。また、所有権割り当てを取り消すこともできます。

IPC 機能、キー引数、および作成フラグ

IPC 機能へのアクセスを要求するプロセスは、識別できなければなりません。これを行うために、IPC 機能を初期化する関数または IPC 機能へのアクセスを提供する関数は、key_t キー引数を使用します。キーは、任意の値または実行時に共通の元になる値から導き出すことができる値です。1 つの方法は、ftok(3C) を使用することです。ftok(3C) は、ファイル名をシステムで固有のキー値に変換します。

メッセージ、セマフォ、および共用メモリの初期化やアクセスを行う関数は、int 型の ID 番号を戻します。IPC 機能の読み取り、書き込み、および制御操作を行う関数は、この ID を使用します。

キー引数に IPC_PRIVATE を指定して関数を呼び出すと、作成プロセス専用の IPC 機能のインスタンスが新しく初期化されます。

フラグ引数として IPC_CREAT フラグを指定すると、関数はその IPC 機能が存在していない場合は新しく作成しようとします。

IPC_CREATIPC_EXCL の両方のフラグを指定して関数を呼び出した場合、その関数は IPC 機能がすでに存在していると失敗します。これは 2 つ以上のプロセスが IPC 機能を初期化する可能性がある場合に便利です。たとえば、複数のサーバプロセスが同じ IPC 機能にアクセスしようとする場合です。サーバプロセスがすべて IPC_EXCL を指定して IPC 機能を作成しようとすると、最初のプロセスだけが成功します。

この 2 つのフラグをどちらも指定しない場合、IPC 機能がすでに存在していれば、アクセスした関数はその機能の ID を戻します。IPC_CREAT を指定しない場合、該当する機能がまだ初期化されていなければ、呼び出しは失敗します。

これらの制御フラグは、論理 (ビット単位) OR を使用して 8 進数値アクセス権モードと組み合わせるとフラグ引数を作成できます。たとえば、次の例では、メッセージ待ち行列が存在していない場合は新しい待ち行列を初期化します。


msqid = msgget(ftok("/tmp", 'A'), (IPC_CREAT | IPC_EXCL | 0400)); 

最初の引数は、文字列 ("/tmp") に基づいてキー (次の 'A') と評価されます。2 番目の引数は、アクセス権と制御フラグが組み合わされたものと評価されます。

System V メッセージ

プロセスがメッセージを送受信できるようにするには、msgget(2) 関数によって待ち行列を初期化しなければなりません。待ち行列の所有者または作成者は、msgctl(2) を使用して、その所有権またはアクセス権を変更できます。また、そうするためのアクセス権を持つプロセスは、制御操作のために msgctl(2) を使用することもできます。

IPC メッセージを使用すると、プロセスはメッセージを送受信し、メッセージを任意の順序で処理待ち行列に入れることができます。パイプで使用されるファイルバイトストリームのモデルによるデータフローとは異なり、IPC メッセージでは長さが明示されます。

メッセージには特定のタイプを割り当てることができます。このため、サーバプロセスは、クライアント PID をメッセージタイプとして使用して、その待ち行列上のクライアント間にメッセージトラフィックを振り向けることができます。単一メッセージトランザクションでは、複数のサーバプロセスは、共用メッセージ待ち行列に送られるトランザクション群に対して、並行して働くことができます。

メッセージの送受信操作は、それぞれ msgsnd(2) 関数と msgrcv(2) 関数によって実行されます。メッセージが送信されると、そのテキストがメッセージ待ち行列にコピーされます。msgsnd(2) 関数と msgrcv(2) 関数は、ブロッキング操作としても非ブロッキング操作としても実行できます。ブロッキングされたメッセージ操作は、次の条件のどれかが生じるまで中断されます。

メッセージ待ち行列の初期化

msgget(2) 関数は、新しいメッセージ待ち行列を初期化します。また、key 引数に対応する待ち行列のメッセージ待ち行列 ID (msgid) を戻します。msgflg 引数として渡される値は、待ち行列アクセス権と制御フラグを設定する 8 進数の整数でなければなりません。

MSGMNI カーネル構成オプションは、カーネルがサポートする固有のメッセージ待ち行列の最大数を指定します。この制限を越えると、msgget(2) 関数は失敗します。次に、msgget(2) 関数の使用例を示します。


#include <sys/ipc.h>
 #include <sys/msg.h>

 ...
 	key_t	key;		/* msgget() に渡す key */
 	int	msgflg,	/* msgget() に渡す msgflg */
 			msqid;	/* msgget() からの戻り値 */ 	...
 	key = ...
 	msgflg = ...
 	if ((msqid = msgget(key, msgflg)) == -1)
 	{
 		perror("msgget: msgget failed");
 		exit(1);
 	} else
 		(void) fprintf(stderr, "msgget succeeded");
 	...

メッセージ待ち行列の制御

msgctl(2) 関数は、メッセージ待ち行列のアクセス権やその他の特性を変更します。msgid 引数は、既存のメッセージ待ち行列の ID でなければなりません。cmd 引数は、次のいずれか 1 つです。

IPC_STAT

待ち行列の状態についての情報を buf が指すデータ構造体に入れる。この呼び出しを行うには、プロセスが読み取り権を持っていなければならない。

IPC_SET

所有者のユーザ ID とグループ ID、アクセス権、およびメッセージ待ち行列の大きさ (バイト数) を設定する。この呼び出しを行うには、プロセスが所有者、作成者、またはスーパーユーザの有効なユーザ ID を持っていなければならない。

IPC_RMID

msgid 引数で指定したメッセージ待ち行列を削除する。

次に、msgctl(2) に各種のフラグをすべて付けて使用する例を示します。


#include			<sys/types.h>
#include			<sys/ipc.h>
#include			<sys/msg.h>

 	...
 	if (msgctl(msqid, IPC_STAT, &buf) == -1)  {
 		perror("msgctl: msgctl failed");
 		exit(1);
 	}
 	...
 	if (msgctl(msqid, IPC_SET, &buf) == -1) {
 		perror("msgctl: msgctl failed");
 		exit(1);
 	}
 	...

メッセージの送受信

msgsnd(2)msgrcv(2) 関数は、それぞれメッセージを送受信します。msgid 引数は、既存のメッセージ待ち行列の ID でなければなりません。msgp 引数は、メッセージのタイプとテキストを含んでいる構造体へのポインタです。msgsz 引数は、メッセージの長さをバイト数で指定します。msgflg 引数には、様々な制御フラグを渡すことができます。

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


#include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>

 ...
 	int				msgflg;	/* 操作用のメッセージフラグ */
 	struct msgbuf 	*msgp;	/* メッセージバッファへのポインタ */
 	size_t			msgsz;	/* メッセージの長さ */
		size_t			maxmsgsize;
 	long				msgtyp;	/* 希望するメッセージタイプ */
 	int				msqid		/* 使用するメッセージ待ち行列の ID */
 	...
 	msgp = malloc(sizeof(struct msgbuf) - sizeof (msgp->mtext) 
							+ maxmsgsz);
 	if (msgp == NULL) {
 		(void) fprintf(stderr, "msgop: %s %ld byte messages.¥n",
 				"could not allocate message buffer for", maxmsgsz);
 		exit(1);
 		...
 		msgsz = ...
 		msgflg = ...
 		if (msgsnd(msqid, msgp, msgsz, msgflg) == -1)
 			perror("msgop: msgsnd failed");
 		...
 		msgsz = ...
 		msgtyp = first_on_queue;
 		msgflg = ...
 		if (rtrn = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) == -1)
 			perror("msgop: msgrcv failed");
 		...

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);
 ...

System V 共用メモリ

SunOS 5.8 オペレーティングシステムで共用メモリアプリケーションを実装するには、mmap(2) 関数と固有仮想記憶管理を利用する方法が最も効率的です。第 6 章「メモリ管理」 を参照してください。

SunOS 5.8 では、System V 共用メモリもサポートされます。共用メモリを使用すると、複数のプロセスが同時に物理メモリの 1 つのセグメントを自分の仮想アドレス空間に接続できますが、効率は低下します。複数のプロセスに書き込みアクセスが許可されているときは、セマフォなどの外部のプロトコルや機構を使用して、不一致や衝突などを防止できます。

プロセスは、shmget(2) を使用して共用メモリセグメントを作成します。この呼び出しは、既存の共用セグメントの ID を取得する際にも使用できます。作成プロセスは、セグメントのアクセス権と大きさ (バイト数) を設定します。

共用メモリセグメントの元の所有者は、shmctl(2) を使用して所有権を他のユーザに割り当てることができます。この関数では割り当てを取り消すこともできます。適切なアクセス権を持っていれば、他のプロセスも shmctl(2) を使用して共用メモリセグメントに様々な制御機能を実行できます。

共用メモリセグメントを作成すると、shmat(2) を使用してプロセスのアドレス空間に接続できます。切り離すには shmdt(2) を使用します。接続するプロセスが shmat(2) のための適当なアクセス権を持っていなければなりません。接続してしまうと、プロセスは接続操作で要求されているアクセス権に従って、セグメントの読み取りまたは書き込みを実行できます。共用セグメントは、同じプロセスによって何回でも接続できます。

共用メモリセグメントは、物理メモリ内のある領域を指す一意の ID を持つ制御構造体から成ります。セグメント識別子を shmid と言います。共用メモリセグメントの制御構造体は、<sys/shm.h> にあります。

共用メモリセグメントのアクセス

shmget(2) を使用して、共用メモリセグメントへアクセスします。呼び出しが成功すると、共用メモリセグメント ID (shmid) を戻します。次に、shmget(2) の使用例を示します。


#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 ...
 	key_t		key;			/* shmget() に渡す key */
 	int		shmflg;		/* shmget() に渡す shmflg */
 	int		shmid;		/* shmget() からの戻り値 */
 	size_t	size;			/* shmget() に渡す大きさ */
 	...
 	key = ...
 	size = ...
 	shmflg) = ...
 	if ((shmid = shmget (key, size, shmflg)) == -1) {
 		perror("shmget: shmget failed");
 		exit(1);
 	} else {
 		(void) fprintf(stderr,
 					"shmget: shmget returned %d¥n", shmid);
 		exit(0);
 	}
 	...

共用メモリセグメントの制御

shmctl(2) を使用して、共用メモリセグメントのアクセス権とその他の特性を変更します。cmd 引数は、次の制御コマンドのいずれか 1 つです。

SHM_LOCK

指定したメモリ内の共用メモリセグメントをロッキングする。このコマンドを実行するプロセスは、有効なスーパーユーザの ID を持っていなければならない。 

SHM_UNLOCK

共用メモリセグメントのロッキングを解除する。このコマンドを実行するプロセスは、有効なスーパーユーザの ID を持っていなければならない。 

IPC_STAT

制御構造体にあるステータス情報を取得して、buf が指すバッファに入れる。このコマンドを実行するプロセスは、セグメントの読み取り権を持っていなければならない。 

IPC_SET

有効なユーザおよびグループの識別子とアクセス権を設定する。このコマンドを実行するプロセスは、所有者、作成者、またはスーパーユーザの有効な ID を持っていなければならない。 

IPC_RMID

共用メモリセグメントを削除する。このコマンドを実行するプロセスは、所有者、作成者、またはスーパーユーザの有効な ID を持っていなければならない。 

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


#include			<sys/types.h>
#include			<sys/ipc.h>
#include			<sys/shm.h>
 ...
 	int		cmd;		/* shmctl() のためのコマンドコード */
 	int		shmid;		/* セグメント ID */
 	struct shmid_ds		shmid_ds;			/* 結果を保持するための共用メモリデータ構造体 */
 	...
 	shmid = ...
 	cmd = ...
 	if ((rtrn = shmctl(shmid, cmd, shmid_ds)) == -1) {
 		perror("shmctl: shmctl failed");
 		exit(1);
 	...

共用メモリセグメントの接続と切り離し

shmat()shmdt() (shmop(2) を参照) を使用して、共用メモリセグメントの接続と切り離しを行います。shmat(2) は、共用セグメントの先頭へのポインタを戻します。shmdt(2) は、shmaddr で指定されたアドレスから共用メモリセグメントを切り離します。次に、shmat(2)shmdt(2) の呼び出しの使用例を示します。


#include			<sys/types.h>
#include			<sys/ipc.h>
#include			<sys/shm.h>

static struct state {	/* 接続されるセグメントの内部レコード */
 	int		shmid;		/* 接続されるセグメントの ID */
 	char		*shmaddr;	/* 接続点 */
 	int		shmflg;		/* 接続時に使用されるフラグ */
 	} ap[MAXnap];			/* 接続されている現在のセグメントの状態 */
 	int		nap;			/* 現在接続されているセグメント数 */
 ...
 	char				*addr;			/* アドレス用の作業変数 */
 	register int				i;			/* 作業領域 */
 	register struct state			*p;				/* 現在の状態エントリへのポインタ */
 ...
 	p = &ap[nap++];
 	p->shmid = ...
 	p->shmaddr = ...
 	p->shmflg = ...
 	p->shmaddr = shmat(p->shmid, p->shmaddr, p->shmflg);
 	if(p->shmaddr == (char *)-1) {
 		perror("shmat failed");
 		nap--;
 	} else
 		 (void) fprintf(stderr, "shmop: shmat returned %p¥n",
 					p->shmaddr);
 	...
 	i = shmdt(addr);
 	if(i == -1) {
 		perror("shmdt failed");
 	} else {
 		(void) fprintf(stderr, "shmop: shmdt returned %d¥n", i);
 		for (p = ap, i = nap; i--; p++) {
 			if (p->shmaddr == addr) *p = ap[--nap];
 		}
 	}
 	...