IPC: セマフォ
セマフォでロックを行い安全にリソースを操作する。
SYNOPSIS
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); int semctl(int semid, int semnum, int cmd, ...); int semop(int semid, struct sembuf *sops, size_t nsops);
DESCRIPTION
セマフォは、共有のリソースを複数のプロセスが同時にアクセスする際の制御(同期を取ったり割り込み処理など)に使用されます。セマフォを使用して、ファイルをロックすることも出来ます。もっとも、セマフォを使用するメリットは共有メモリのデータを管理できるところにあります。
● セマフォのセットの獲得には、semget()
システムコールを使用します。
int semget(key_t key, int nsems, int semflg);
semget()
システムコールは、セマフォの獲得に成功するとユニークな ID を返し、失敗すると -1
を返します。
key
の値が IPC_PRIVATE
もしくは key
に対応するセマフォが存在しない時、semflg
に IPC_CREAT
が指定されているとセマフォ識別子とそれに対応した nsems
個のセマフォを持つデータ構造体とデータセットが key
に対して生成されます。semflg
には、セマフォへのアクセスパーミッションも指定できます。
と semflg
に IPC_CREATIPC_EXCL
を指定した場合、key
がそれに対応するセマフォ識別子をすでに持っている場合エラーを返します(open()
システムコールと同じ様な動き)。
● セマフォ・セットの操作は、semop()
システムコールを使用します。
int semop(int semid, struct sembuf *sops, size_t nsops);
semid
は、semget()
システムコールによって獲得されたセマフォ・セットの ID です。sops
は、セマフォを操作する構造体の配列へのポインタを指定します。nsops
は、配列内の構造体の数です。
struct sembuf { ushort sem_num; /* semaphore number */
short sem_op /* semaphore operation */
short sem_flg; /* operation flags */ }
セマフォ・セットの操作は、sem_num
番目のセマフォに対して sem_op
で指定した操作を行います。詳しい説明に入るまえに、セマフォは次のメンバーからなる sem
という名前のデータ構造体であることを理解している必要があります。
ushort_t semval; /* セマフォ値 */ pid_t sempid; /* 最後に操作を行なったプロセスの PID */ ushort_t semncnt; /* semval 値が現在の値より大きくなるまで待つプロセス数 */ ushort_t semzcnt; /* semval == 0 となるのを待つプロセス数 */
0 | semval が0の場合、 semop() は sops で指定する次のセマフォ操作を開始します。 最後の操作である場合はすぐに制御を返します。 semval が0ではなく ( sem_flg & IPC_NOWAIT) が"true"の場合、 semop() はすぐに制御を返します。 semval が0ではなく ( sem_flg & IPC_NOWAIT) が"false"の場合、 semop() は「semval が0になる場合」「呼び出したプロセスが待機状態にある semid が、システムから取り除かれた場合」「呼び出したプロセスが受け取るはずのシグナルを受信した場合」のいずれかが起こるまで、指定したセマフォに対応するsemzcntを1増やし、 呼び出したプロセスの実行を停止します。 |
ポジティブ ( >0 ) | sem_op の値が semval に加算され、 ( sem_flg & SEM_UNDO) が"true"の場合、 呼び出したプロセスの指定セマフォに対する semadj の値から sem_op の値が引かれます。 |
ネガティブ ( <0 ) | semval が sem_op の絶対値に等しいか、またはそれ以上の場合、 sem_op の絶対値が semval から減算されます。 同様に、 ( sem_flg & SEM_UNDO) が"true"のとき、 sem_op の絶対値が、呼び出したプロセスの 指定セマフォに対する semadj の値に加算されます。 semvalが sem_op の絶対値より小さく ( sem_flg & IPC_NOWAIT) が"true"の場合、semop() はすぐに値を返します。 semvalが sem_op の絶対値より小さく ( sem_flg & IPC_NOWAIT) が"false"の場合、 semop() は「semvalが sem_op の絶対値に等しいか、またはそれ以上になった場合」「呼び出したプロセスが待機状態にある semid が、システムから取り除かれた場合」「呼び出したプロセスが受け取るはずのシグナルを受信した場合」のいずれか1つが発生するまで、 指定セマフォに対応するsemncntを1増やし、 呼び出したプロセスの実行を停止します。 |
sem_flg には IPC_NOWAIT
と SEM_UNDO
が設定できます。 SEM_UNDO
が指定された操作はそのプロセスが終了した時点でもとに戻されます。
● セマフォの破棄は、 semctl()
システムコールを使用します。
int semctl(int semid, int semnum, int cmd, ...);
semid
は、semget()
システムコールによって獲得されたセマフォ・セットの ID です。semnum
で指定したセマフォに対してコマンドを実行します。semctl()
システムコールは cmd
で指定されるさまざまなセマフォ制御操作を行ないます。 4番目の引き数は省略可能で、要求する操作に従って変わります。指定する場合には、アプリケーションが明示的に宣言した union semun
型のデータでなければなりません。
union semun { int val; /* used for SETVAL only */ struct semid_ds *buf; /* for IPC_STAT and IPC_SET */ ushort_t *array; /* used for GETALL and SETALL */ }; semctl(int semid, int semnum, int cmd, union semun arg);
破棄する場合、cmd
に IPC_RMID
を指定します。
セマフォの破棄は、ipcs
コマンドで詳細を調べ ipcrm
コマンドを利用しても可能です。
SAMPLE
セマフォを作成するプログラム
(seminit.c
)
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> union semun { int val; struct semid_ds *buf; ushort_t *array; }; int main() { key_t key; int semid; union semun arg; if ((key = ftok("sem.dat", 'S')) == -1) { perror("ftok"); exit(1); } /* create a semaphore set with 1 semaphore: */ if ((semid = semget(key, 1, 0666 | IPC_CREAT)) == -1) { perror("semget"); exit(1); } /* initialize semaphore #0 to 1: */ arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(1); } return 0; }
セマフォによってロックをかけるプログラム
(semtest.c
)
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int main() { key_t key; int semid; struct sembuf sb = {0, -1, 0}; /* set to allocate resource */ if ((key = ftok("sem.dat", 'S')) == -1) { perror("ftok"); exit(1); } /* grab the semaphore set created by seminit */ if ((semid = semget(key, 1, 0)) == -1) { perror("semget"); exit(1); } printf("Press return to lock: "); getchar(); printf("Trying to lock... "); if (semop(semid, &sb, 1) == -1) { perror("semop"); exit(1); } printf("Locked.\n"); printf("Press return to unlock: "); getchar(); sb.sem_op = 1; /* free resource */ if (semop(semid, &sb, 1) == -1) { perror("semop"); exit(1); } printf("Unlocked\n"); return 0; }
作成したセマフォを破棄するプログラム
(semrm.c
)
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> union semun { int val; struct semid_ds *buf; ushort_t *array; }; int main() { key_t key; int semid; union semun arg; if ((key = ftok("sem.dat", 'S')) == -1) { perror("ftok"); exit(1); } /* grab the semaphore set created by seminit */ if ((semid = semget(key, 1, 0)) == -1) { perror("semget"); exit(1); } /* remove */ arg.val = 1; if (semctl(semid, 0, IPC_RMID, arg) == -1) { perror("semctl"); exit(1); } return 0; }
これをコンパイルし実行すると以下のようになります。
<< SHELL-1 >> # gcc seminit.c -o seminit # gcc semtest.c -o semtest # gcc semrm.c -o semrm # touch sem.dat # ./seminit # ./semtest Press return to lock: <Enter> Trying to lock... Locked. Press return to unlock:
SHELL-1 でロックをかけるプログラムが起動しロックしたら、別のシェルで同じプログラムを起動します。
<< SHELL-2 >> # ./semtest Press return to lock: <Enter> Trying to lock...
すると SHELL-2 では SHELL-1 でロックが解除されるのを待っています。 SHELL-1 で、<Enter> をタイプしてロックを解除すると SHELL-2 でロックがかかります。 テストが終わったら semrm
で作成したセマフォ・セットを破棄してください。
SEE ALSO
semget(2), semctl(2), semop(2), intro(2)