Linuxシステムコール、セマフォの使い方

UNIX / Linux システムコール・プログラミング

プロセス間通信(IPC)、セマフォ

セマフォとは?

セマフォ、聞き慣れない言葉ですが大昔の鉄道の信号機のことです。


単線区間で列車同士が衝突しないようにするため、信号機が使われました。
意味合い的には同様で、コンピューター内のある資源(メモリなど)に別々のプロセスが同時にアクセスしないようにするために利用します。

セマフォはSYSTEM V IPC です。

この、セマフォを利用することで複数のプロセスが同期して処理を進めることができます。

Linuxのセマフォは、セマフォ集合として生成して利用します。
このため、1つのセマフォ集合に複数のセマフォを含めることができ、Linuxのセマフォはカウンティングセマフォであるため、複数の許可を与えることも可能です。

ただし、この記事のサンプルコードは1つの集合に1つだけのセマフォ、許可数は1つだけとなっています。

まず、セマフォ生成のサンプルコードを示します。

seminit.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
 
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *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;
}

生成されたセマフォは下記のコマンドで確認することができます。
$ ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x530120c6 0 hogehoge 666

このサンプルコードでは、"sem.dat"ファイルを ftok() でキーを生成していますので、事前に touch コマンドなどでファイルを作っておいてください。
$touch sem.dat

このコードは、ftok() で生成したキーを使って1つのセマフォを持つセマフォ集合を生成しています。
 semget(key, 1, 0666 | IPC_CREAT)
  key、キー
  1、生成するセマフォの数
  0666 | IPC_CREAT、パーミッションと生成

そして、セマフォの初期設定をしています。
 semctl(semid, 0, SETVAL, arg)
  semid、semget()で得られたID
  0、操作するセマフォの指定=0番目を指定
SETVAL、指定したセマフォに次のarg.valを設定する
  arg、valに1」を設定

次に、このセマフォを使って2つのプロセスが同期して処理を進めるサンプルコードを示します。

semproc.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, char** argv) {

    key_t         key;
    int           semID;
    struct sembuf sop;

    sop.sem_num =  0;            // Semaphore number
    sop.sem_op  = -1;            // Semaphore operation is Lock
    sop.sem_flg =  0;            // Operation flag

    // Create key 
    if ((key = ftok("sem.dat", 'S')) == -1) {
        perror("ftok");
        exit(-1);
    }
 
    // Get created semaphore
    if ((semID = semget(key, 1, 0)) == -1) {
        perror("semget");
        exit(-2);
    }
 
    printf("Be going to lock--- -\n");

    // Try to lock semaphore
    if (semop(semID, &sop, 1) == -1) {
        perror("semop()");
        if(errno == EIDRM) {
            printf("errno=EIDRM, ERRNO=%d\n", errno);
            printf("  I'm going to CANCEL this procedure.\n");
            return(1);
        }
        else {
            exit(-3);
        }
    }
    printf("--Locked!\n\n");

    // You are proccesing code.
    printf("Press return --> Unclock, and quit self\n");
    getchar();

    // Try to release semaphore
    sop.sem_op = 1;
    if (semop(semID, &sop, 1) == -1) {
        perror("semop");
        exit(1);
    }
 
    printf("--Unlocked\n");
 
    return 0;
}

このコードは、"seminit.c"で生成されたセマフォを取得します。そして、semop()を使って、そのセマフォに対してロック操作を行います。この場合、セマフォに対して-1を行うため、"seminit.c"が設定した初期状態、1から1減算して0となります。0以上であればセマフォ操作を完了して制御がプログラムに戻ります。

 semop(semID, &sop, 1)
  semID、セマフォID
  sop、セマフォの操作、sop.sem_op = -1 で減算
  1、セマフォ集合の数

このコードはその後、ユーザーからの入力を待ち、再度semop()を使ってセマフォのアンロック操作を行います。アンロック操作では、セマフォに1加算するため、1に戻ります。

 semop(semID, &sop, 1)
  semID、セマフォID
  sop、セマフォの操作、sop.sem_op = 1 で加算
  1、セマフォ集合の数

プロセスが1つだけの場合では全く問題ありません。

では、この"semproc.c"を実行してユーザーからの入力を待っている状態としてください。そして、別のターミナルを開いて"semproc.c"を実行してください。
2つ目の"semproc.c"がセマフォをロックしようとするところで待たされます。これは、2つ目の"semproc.c"はセマフォを1減算すると、セマフォの値が0以下となるため制御がユーザープログラムに戻ってこない状況になっています。
 
この状態で、1つ目の"semproc.c"に対してリターンを入力してプログラムを進めると、2つ目の"semproc.c"に制御が戻り、ユーザー入力の待ちとなります。これは、1つ目の"semproc.c"がセマフォをアンロックしセマフォを1加算して0に戻り、セマフォを待っていた最初のプロセスに対して制御が戻るためです。

いくつかのターミナルを開いて動作を確認してみてください。ユーザーからの入力を受け付けられるターミナルは必ず1つだけです。

最後に、使い終わったセマフォを削除するコードを示します。

semrm.c
#include 
#include 
#include 
#include 
#include 
#include 
 
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *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) == -1) {
        perror("semctl");
        exit(1);
    }
 
    return 0;
}

semctl(semid, 0, IPC_RMID)
 semid、セマフォID
 0、IPC_RMIDの場合無視される
 IPC_RMID、全てのセマフォを削除、待っているプロセスは制御が戻る

このサンプルコードでも、セマフォロックを待っているプロセスがIPC_RMIDにより制御が戻るようになっています。

また、セマフォの削除は ipcs コマンドで確認したキーまたはIDを使って、ipcrm コマンドで削除することもできます。



セマフォと同様に排他制御を行うためのmutexについては、以下を!
 → 「mutex(ミューテックス)とは?





Have a Happy Hucking!!


Lightning Brains

コメント

このブログの人気の投稿

Linuxシステムコール、共有メモリの使い方

Linuxシステムコール、メッセージキューの使い方