SunOS 5.9 およびその互換オペレーティングシステムは、System V のプロセス間通信 (IPC) パッケージも提供します。System V IPC は事実上 POSIX 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 機能へのアクセスを初期化または提供するインタフェースは key_t というキー引数を使用します。キーは、任意の値または実行時に共通の元になる値から導き出すことができる値です。このようなキーは、ftok(3C) を使用して、ファイル名をシステム内で一意のキー値に変換することで導くこともできます。
メッセージ、セマフォ、または共有メモリーへのアクセスを初期化または取得するインタフェースは int 型の ID 番号を返します。IPC インタフェースの読み取り、書き込み、および制御操作を行う関数は、この ID を使用します。
キー引数に IPC_PRIVATE を指定して関数を呼び出すと、作成プロセス専用の IPC 機能のインスタンスが新しく初期化されます。
呼び出しに適切なフラグ引数として IPC_CREAT フラグを指定した場合、IPC 機能が存在していなければ、インタフェースはその IPC 機能を新たに作成しようとします。
IPC_CREAT と IPC_EXCL の両方のフラグを指定してインタフェースを呼び出した場合、IPC 機能がすでに存在していれば、インタフェースは失敗します。この動作は複数のプロセスが IPC 機能を初期化する可能性がある場合に便利です。たとえば、複数のサーバプロセスが同じ IPC 機能にアクセスしようとする場合です。サーバープロセスがすべて IPC_EXCL を指定して IPC 機能を作成しようとすると、最初のプロセスだけが成功します。
IPC_CREAT と IPC_EXCL の 2 つのフラグをどちらも指定しない場合、IPC 機能がすでに存在していれば、インタフェースはその機能のID を返して、アクセスを取得できるようにします。IPC_CREAT を指定しない場合、該当する機能がまだ初期化されていなければ、呼び出しは失敗します。
論理 (ビット単位) OR を使用すると、IPC_CREAT と IPC_EXCL を 8 進数のアクセス権モードと組み合わせることによってフラグ引数を作成できます。たとえば、次の例では、メッセージ待ち行列が存在していない場合は新しい待ち行列を初期化します。
msqid = msgget(ftok("/tmp", 'A'), (IPC_CREAT | IPC_EXCL | 0400));
最初の引数は、文字列「"/tmp"」に基づいてキー「 'A'」と評価されます。2 番目の引数は、アクセス権と制御フラグが組み合わされたものと評価されます。
プロセスがメッセージを送受信できるようにするには、msgget(2) を使用して待ち行列を初期化する必要があります。待ち行列の所有者または作成者は msgctl(2) を使用して、所有権またはアクセス権を変更できます。アクセス権を持つプロセスは msgctl(2) を使用して、操作を制御できます。
IPC メッセージを使用すると、プロセスはメッセージを送受信し、メッセージを任意の順序で処理待ち行列に入れることができます。パイプで使用されるファイルバイトストリームのモデルによるデータフローとは異なり、IPC メッセージでは長さが明示されます。
メッセージには特定のタイプを割り当てることができます。このため、サーバープロセスはクライアントプロセス ID をメッセージタイプとして使用することによって、その待ち行列上の クライアント間にメッセージトラフィックを振り向けることができます。単一メッセージトランザクションでは、複数のサーバープロセスは、共有メッセージ待ち行列に送られるトランザクション群に対して、並行して働くことができます。
メッセージを送受信する操作はそれぞれ 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 つです。
待ち行列の状態の情報を buf が指すデータ構造体に入れる。この呼び出しを行うには、プロセスが読み取り権を持つ必要がある。
所有者のユーザー ID とグループ ID、アクセス権、およびメッセージ待ち行列の大きさ (バイト数) を設定する。この呼び出しを行うには、プロセスが所有者、作成者、またはスーパーユーザーの有効なユーザー ID を持つ必要がある。
次のコードに、さまざまなフラグをすべて指定した 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 IPC セマフォは、大きな配列の中に存在できるため、極めて重いセマフォです。より軽量なセマフォはスレッドライブラリ (semaphore(3THR) のマニュアルページを参照) で利用できます。また、POSIX セマフォは System V セマフォの最新の実装です (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 が既存の配列の要素数を超えると呼び出しは失敗します。正しい数がわからない場合は、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 引数は、次のいずれかの制御フラグです。
単一セマフォの値を戻す
単一セマフォの値を設定する。この場合、arg は int 値の arg.val と解釈される
セマフォまたは配列に対して最後に操作を実行したプロセスの PID を戻す
セマフォの値が増加するのを待っているプロセス数を戻す
特定のセマフォの値が 0 に達するのを待っているプロセス数を戻す
セット内のすべてのセマフォの値を戻す。この場合、arg は unsigned short 値の配列へのポインタである arg.array と解釈される
セット内のすべてのセマフォに値を設定する。この場合、arg は unsigned short 値の配列へのポインタである arg.array と解釈される
制御構造体からセマフォのセットの状態情報を取得し、semid_ds 型のバッファーへのポインタ arg.buf が指すデータ構造体に入れる
有効なユーザーおよびグループの識別子とアクセス権を設定する。この場合、arg は arg.buf と解釈される
指定したセマフォのセットを削除する
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 構成オプションで指定されます。SEMOPM オプションは単一の semop(2) 呼び出しで指定できる最大操作数で、デフォルトでは 10 に設定されています。
実行する操作は、次のように判断されます。
正の整数の場合は、セマフォの値をその数だけ増加する
負の整数の場合は、セマフォの値をその数だけ減少する。セマフォを 0 未満の値に設定しようとすると、IPC_NOWAIT が有効であるかどうかによって、失敗するかブロックされる
値が 0 の場合は、セマフォの値が 0 になるのを待つ
semop(2) で使用できる制御フラグは IPC_NOWAIT と SEM_UNDO の 2 つです。
IPC_NOWAIT |
配列内のどの操作についても設定できる。IPC_NOWAIT が設定されている操作を実行できなかった場合、セマフォの値を変更せずにインタフェースを戻す。セマフォを現在の値より多く減らそうしたり、セマフォが 0 でないときに 0 かどうか検査しようとするとインタフェースは失敗する |
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); ...
SunOS 5.9 オペレーティングシステムで共有メモリーアプリケーションを実装するには、mmap(2) とシステムの内蔵仮想メモリー機能を利用する方法が最も効率的です。詳細は、第 1 章「メモリー管理」を参照してください。
SunOS 5.9 は System V 共有メモリーもサポートしますが、物理メモリーのセグメントを複数のプロセスの仮想アドレス空間に接続する方法としては最適ではありません。複数のプロセスに書き込みアクセスが許可されているときは、セマフォなどの外部のプロトコルやメカニズムを使用して、不整合や衝突などを回避できます。
プロセスは、shmget(2) を使用して共有メモリーセグメントを作成します。この呼び出しは、既存の共有セグメントの ID を取得する際にも使用できます。作成プロセスは、セグメントのアクセス権と大きさ (バイト数) を設定します。
共有メモリーセグメントの元の所有者は、shmctl(2) を使用して所有権をほかのユーザーに割り当てることができます。所有者はこの割り当てを取り消すこともできます。適切なアクセス権を持っていれば、ほかのプロセスも shmctl(2) を使用して共用メモリーセグメントにさまざまな制御機能を実行できます。
共有メモリーセグメントを作成した後は、shmat(2) を使用してプロセスのアドレス空間にセグメントを接続できます。切り離すには shmdt(2) を使用します。プロセスを接続するには shmat(2) に対しての適当なアクセス権を持つ必要があります。接続すると、プロセスは接続操作で要求されているアクセス権に従って、セグメントの読み取りまたは書き込みを実行できます。共有セグメントは、同じプロセスによって何回でも接続できます。
共有メモリーセグメントは、物理メモリー内のある領域を指す一意の ID を持つ制御構造体から成ります。セグメント 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 およびグループ ID とアクセス権を設定する。このコマンドを実行するプロセスは、所有者、作成者、またはスーパーユーザーの有効な 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]; } } ...