セマフォとは何か
セマフォは、複数のプロセスやスレッドが共有リソースにアクセスする際の競合を防ぐための同期機構です。データの送信自体を目的とするものではなく、主に「排他制御」と「同期」を実現するために使用されます。内部的には単なるカウンター(整数値)を持ち、その値に基づいてプロセスの実行可否を判断します。
基本動作:P操作とV操作
- P操作(Proberen: 試す):
セマフォの値を1減らします。値が0以下になる場合、プロセスはブロック(待機状態)になります。 - V操作(Verhogen: 増やす):
セマフォの値を1増やし、待機中のプロセスがあればそれを起床させます。
これらの操作はアトミック(原子的)に行われます。つまり、途中で割り込まれることなく一連の処理が完遂されるため、競合状態(race condition)が発生しません。
主要なシステムコール
1. セマフォ集合の生成または取得 — semget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
key:識別子。通常ftok()で生成するか、IPC_PRIVATEを使用。nsems:作成するセマフォの数(例:2ならインデックス0と1を持つ集合)。semflg:フラグ。例:IPC_CREAT | 0666(存在しなければ作成)。
成功時は非負のセマフォIDを返し、失敗時は-1を返します。
2. P/V操作の実行 — semop()
int semop(int semid, struct sembuf *sops, size_t nsops);
sembuf構造体の定義:
struct sembuf {
unsigned short sem_num; // セマフォ番号(0からnsems-1)
short sem_op; // 操作値(-1=P, +1=V)
short sem_flg; // フラグ(0: ブロッキング, IPC_NOWAIT: ノンブロッキング)
};
例:インデックス0のセマフォに対してP操作を行う。
struct sembuf op = {0, -1, 0};
semop(semid, &op, 1);
3. セマフォの制御と削除 — semctl()
int semctl(int semid, int semnum, int cmd, ...);
代表的なコマンド:
- SETVAL:指定したセマフォの値を設定。
- GETVAL:現在の値を取得。
- IPC_RMID:セマフォ集合を削除。
union semunが必要ですが、環境によって未定義の場合があるため、次のように自分で定義することが推奨されます。
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
例:初期値を5に設定。
union semun arg;
arg.val = 5;
semctl(semid, 0, SETVAL, arg);
よくある問題と対処法
- Invalid argument (EINVAL):
同じキーで異なるnsemsでsemget()を呼び出した場合。既存のセマフォが残っている可能性があるため、以下のコマンドで確認・削除。
ipcs -s # セマフォの一覧表示
ipcrm -s <semid> # 特定のセマフォを削除
実践コード例
以下は、セマフォを使ってプロセス間の同期を行うシンプルな例です。
初期化とV操作のサンプル(sender側)
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
// union semun の手動定義
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
key_t key = ftok("keyfile", 'A');
if (key == -1) {
perror("ftok");
exit(1);
}
// セマフォ集合を作成(1個のセマフォ)
int sid = semget(key, 1, IPC_CREAT | 0666);
if (sid == -1) {
perror("semget");
exit(1);
}
// 初期値を0に設定
union semun arg;
arg.val = 0;
semctl(sid, 0, SETVAL, arg);
printf("セマフォ初期化完了。V操作を実行して起床。\n");
// V操作:値を+1 → 待機中のプロセスを起床
struct sembuf op = {0, 1, 0};
semop(sid, &op, 1);
// 後片付け
semctl(sid, 0, IPC_RMID);
return 0;
}
P操作による待機(receiver側)
// (同様にkeyを生成)
int sid = semget(key, 1, 0); // 既存のセマフォを取得
printf("P操作を開始:信号待ち...\n");
struct sembuf op = {0, -1, 0};
semop(sid, &op, 1); // 値が0ならここでブロック
printf("受け取りました!同期成功。\n");
// 削除はsender側で行われるため不要
このように、セマフォはプロセス間の順序制御や資源の排他アクセスに強力な手段を提供します。