最近の Linux などではほぼ標準的に備わっているSystem V 由来の IPC (InterProcessCommunication) に関する事項
概要
SystemV IPC を使用する場合の操作概要
- システム的に一意なキーIDを取得する。
- 取得したキーIDを利用し、各資源に対応したIDを取得する。
- 資源に対応するID値を使用し、操作を行う。
- 必要が無くなったらID値を破棄する。
# キーIDと資源毎に確保されるIDは関連はあるが別物。
キーID の取得
キーIDを取得するには、二通りの方法がある。
- 常に一意なIDを生成する、IPC_PRIVATE 定数を使用する方法
- ftok 関数を利用してシステム上一意なIDを取得する方法。
key_t ftok ( const char *pathname, int proj_id );
ファイルパスと特定の識別子(数値)から一意なキーIDを取得する。
pathname は、ファイルシステム上に存在し、アクセス可能なファイルそのものを差す。
proj_id は、0 以外の一意なID。(システムに依るんだろうけど)低位 8bit しか使われないらしい。
要するに、ファイルパスから得られる識別情報と salt 的に使われるID値の掛け合わせを返す。
# 実装上ではファイルの inode 値と proj_id が使われていたりするみたい
pathname が同じであっても必ず同じのIDが取得できるとは限らなかったり、pathname が別であっても必ず別のIDが取得できるとは限らなかったりする。
# 詳しくは man でも見てください。
失敗時、-1 が返る。これをそのまま各資源の取得に使用不可能だと思ってると痛い目を見ることもある。
# 下のコード、Solaris では動く
semid = semget ( ftok ( "/path/not/available", 0x01 ), IPC_CREAT ); /* "/path/not/available" は、存在しないファイル。 * ここでは、ftok失敗 → semget失敗を期待していた。 */
IPC資源
SystemV IPC操作で取り扱い可能な資源は以下の三種類がある。
- 共有メモリ
- 共有メモリセグメントを割り当てる
- セマフォ
- 同期のためのセマフォ(集合)を操作する
- メッセージキュー
- キューによるメッセージングを行う
資源の種類によっては若干違う部分も有るけど、基本的にはだいたい同じ。
- xxxget によって資源のIDを取得する
- (操作はやっぱりそれぞれ違う名前)
- xxxctl によって資源に関する設定を行う(破棄も)
取得
各資源毎に関数名は違うけど、引数はほぼ同じです。
int shmget ( key_t key, int size, int shmflg ); int semget ( key_t key, int nsems, int semflg ); int msgget ( key_t key, int msgflg );
key は、ftok で取得したキーID か IPC_PRIVATE 定数を使用する。
xxxflg は、全資源共通で以下のフラグと、各資源に対するアクセス許可を設定する。
# アクセス許可はファイルなどに使用するものと同様(実行bit は無視)だが、それぞれ MSGR, MSGW 等の定義が用意されている。
- IPC_CREAT
- 資源が存在しない場合に、新しい資源を生成する。
IPC_EXCLが指定されていなければ、既に存在する資源のIDを返す。 - IPC_EXCL
- IPC_CREATと共に指定する。既に資源が存在する場合に失敗する。
# open(2) と同様に判断される。 [ IPC_CREAT ]◇—<< deploy >>—[ IPC_EXCL ]な感じ(ちょと違うか?)
shmget の場合
size には、作成する場合には共有メモリセグメントのサイズを指定する(0以上)。
単に既存の共有メモリセグメントを取得する場合には 0 を指定する。
semget の場合
nsems には、作成するセマフォ集合の個数を指定する(0以上)。
単に既存のセマフォ(集合)を取得する場合には 0 を指定する。
操作
各資源によって、操作はそれぞれバラバラです。
# 当然かw
共有メモリ操作
共有メモリは、一度割り当ててしまえば他のポインタと同じ様に操作可能な共有メモリセグメントを提供する。
void *shmat ( int shmid, const void *shmaddr, int shmflg );
shmat 関数によって、shmid で指定された共有メモリセグメントを Attach する。
shmaddr がNULL ならば、新規に割り当てられた共有メモリセグメントの先頭アドレスが返る。
shmaddr がNULL ではなく、shmflg に SHMRND が指定されていなければ、共有メモリセグメントは shmaddr にアタッチされる。
shmaddr がNULL ではなく、shmflg に SHMRND が指定されていれば、共有メモリセグメントは shmaddr で指定されたアドレスを SHMLBA 定数で切り下げたアドレスにアタッチされる。
shmflg に SHM_RDONLY が指定されている場合、共有メモリセグメントは読み込み専用にアタッチされる。
int shmdt ( const void *shmaddr );
shmdt 関数によって、shmat でアタッチした共有メモリセグメントを Detach する。
(デタッチは削除とは違うと言うことに注意。デタッチされた共有メモリセグメントも、IPC_RMID されるまで削除されない)
fork 時には共有メモリセグメントは子プロセスに継承され、exec, exit の場合には共有メモリセグメントはデタッチされる。
セマフォ操作
セマフォ(待ち行列)は、アトミックな操作を行う際の待ち合わせに良く利用される。
int semop ( int semid, struct sembuf *sops, unsigned nsops );
一度の semop で複数のセマフォの操作を行うことができるらしい。
# sops に配列・nsops で配列数指定で・・・・使ったこと無いから不明だけど。
sops.sem_num にてセマフォ集合の何番目(0が最初)に対して操作するかを指定する。
sops.semop にてセマフォをどの様に操作するかを指定する。正の数ならば、semval に semop の値を追加する。負の数且つ semval が semop の絶対値以上で有れば、semval から semop の絶対値を引いて返り、semval が semop の絶対値未満の場合は semval が semop の絶対値以上になるまで待機する。0を指定した場合は semval が 0 になるまで待機する。
sops.semflg に SEMUNDO が指定されていれば、semop (!=0の場合)の動作毎にUNDO数を更新し、IPCNOWAITが指定された場合、例えば abs(semop) > semval の場合に待機せずに EAGAIN errno にて返る。
fork 時にはプロセスの sem_undo 構造体は継承されない。execve 時には継承される。
メッセージキュー操作
一番 IPC らしい、メッセージパッシング手段を提供する。
int msgsnd ( int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg ); ssize_t msgrcv ( int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg );
それぞれ、見ての通りの送信・受信を行う。
struct msgbuf はシステムに依っては定義されている場合といない場合がある。
というかそもそも、msgbuf はユーザ側で(使いやすい構造体を)定義する様な仕様になっている。
struct msgbuf { long mtype; /* メッセージタイプ(0指定不可) */ char mtext[1]; /* メッセージデータ(参考:F-FAQ 2.6) */ };
mtype は、実際のメッセージ内容ではなくメッセージタイプ(識別IDと言った方が分かりやすいかも?)を格納する。
(おそらくmsgrcvの際に判断する為に:後述) mtype は 0 よりも大きい値を指定する必要がある。
msgsz は msgp のサイズを示すが、これは msgp->mtype のサイズを含めない数で有ることに注意。
msgrcv の際、msgtyp でメッセージ受信の方法(というかメッセージの識別ID?)を選ぶことが出来る。
- msgtyp == 0
- キューの先頭のメッセージを読み込む
- msgtyp > 0
- キューの最初の msgp->mtype == msgtyp のメッセージを受信する。
- msgtyp < 0
- msgp->mtype が一番小さく、且つ abs(msgtyp) よりも小さい、キューの最初のメッセージを受信する。
msgflg 引数には次の様な指定が出来る。
- IPC_NOWAIT
- キューが空の場合に、受信待ちをしないで ENOMSG errno にて返る。
- MSG_EXCEPT
- msgrcv で msgtyp > 0 の場合に、取得するメッセージを msgp->mtype != msgtyp を取得する。
- MSG_NOERROR
- msgsz バイトよりも長いメッセージを切りつめる。
制御
取得の場合と似たような感じです。
xxxid で指定した資源に対して、cmd で指定された制御操作を実行します。
int shmctl ( key_t shmid, int cmd, struct shmid_ds *buf ); int semctl ( key_t semid, int semnum, int cmd, ...[union semun arg] ); int msgctl ( key_t msqid, int cmd, struct msqid_ds *buf );
cmd には以下のフラグを指定することが出来ます。
- IPC_STAT
- 各資源に対応するデータを引数の buf (semctl では arg.buf で有ることに注意)に格納する。
- IPC_SET
- 各資源に対応するデータのうちのいくつかを、buf で示すデータに設定する。
設定可能なデータは、buf.xxx_perm.uid, buf.xxx_perm.gid, buf.xxx_perm.mode のみ。 - IPC_RMID
- 各資源を削除する。
各資源に対して、「待ち」の状態にあったプロセスがあれば、EIDRM errno にて終了する。
semctl の場合
セマフォの場合は、何番目のセマフォに対する操作か(最初は0)、を指定することもある(常に、ではない)。
引数は、3個若しくは4個で、その際には union semun を使用する。
この union semun は、ほとんどのシステムで標準のヘッダに定義されていない(define で定義、という手もあったりする)為、以下の様な定義をユーザが行う必要がある。
union semun { int val; /* SETVAL の値 */ struct semid_ds *buf; /* IPC_STAT, IPC_SET 用のバッファ */ unsigned short *array; /* GETALL, SETALL 用の配列 */ };
Linux の場合はもうちょっと多かったりする。
また、セマフォの場合は他の資源に比べて多種多様な制御操作が用意されている。
制御の結果は、arg に返ったり引数に返ったりする。(以下、特に指定が無い場合は関数返値で値が返る)
- GETALL
- 集合の全ての semval の値を arg.array に返す。
- GETNCNT
- 集合の semnum 番目のセマフォの semncnt の値(semvalの増加待ちを行っているプロセス数)を返す。
- GETPID
- 集合の semnum 番目のセマフォの sempid の値(最後にセマフォの操作を行ったプロセスのPID)を返す。
- GETVAL
- 集合の semnum 番目のセマフォの semval の値を返す。
- GETZCNT
- 集合の semnum 番目のセマフォの semzcnt の値(semvalが0になるのを待っているプロセス数)を返す。
- SETALL
- 集合の全てのセマフォの semval に arg.array で指定された値を設定する。
- SETVAL
- 集合の semnum 番目のセマフォの semval に arg.val の値を設定する。
コマンドライン
コマンドラインからIPC資源に対する操作(というか主に確認)をする為のコマンドラインが存在します。
- ipcs
- IPC資源の情報(どれだけ取得されてるとか、どのプロセスが参照してるとか)を表示する。
- ipcrm
- IPC資源を解放(削除)する。
ネタ元
- 「UNIX ネットワークプログラミング Vol2」はとりあえず読んどけ。
- JM Project 書くときに参考にしました。