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 に対応するセマフォが存在しない時、semflgIPC_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_NOWAITSEM_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);

破棄する場合、cmdIPC_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)

 

戻る