SEMOP(2)

名前
semop, semtimedop - System V セマフォの操作

書式
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, size_t nsops);

int semtimedop(int semid, struct sembuf *sops, size_t nsops,
               const struct timespec *timeout);

glibc 向けの機能検査マクロの要件 (feature_test_macros(7) 参照):

semtimedop(): _GNU_SOURCE

説明
System V セマフォ集合 (semaphore set) のメンバーの各セマフォは 以下の関連情報を持っている:

unsigned short  semval;   /* セマフォ値 */
unsigned short  semzcnt;  /* ゼロを待つプロセス数 */
unsigned short  semncnt;  /* 増加を待つプロセス数 */
pid_t           sempid;   /* 最後に操作を行なったプロセスの ID */

semop() は semid で指定されたセマフォ集合の選択されたセマフォに対して操作を行う。 sops は nsops 個の要素の配列を指し、配列の各要素は個々のセマフォに対する操作を示す構造体である。その型は struct sembuf で、次のメンバを持つ:

unsigned short sem_num;  /* セマフォ番号 */
short          sem_op;   /* セマフォ操作 */
short          sem_flg;  /* 操作フラグ */

sem_flg には IPC_NOWAIT と SEM_UNDO が設定できる。 SEM_UNDO が指定された操作は、そのプロセスが終了した時に自動的に取り消される。

sops に含まれる操作の集合は、 配列の順序 で、 アトミックに 実行される。 すなわち、全ての操作が完全に実行されるか、全く実行されないかの どちらかとなる。 全ての操作が直ちに実行できない場合のこのシステムコールの振る舞いは 個々の操作の sem_flg フィールドに IPC_NOWAIT が存在するかによって決まり、後述のようになる。 それぞれの操作はセマフォ集合の sem_num番目 のセマフォに対して実行される。セマフォ集合の最初のセマフォには 番号 0 が振られる。 そして操作は三種類あり、 sem_op の値で区別される。

sem_op が正の整数の場合、その値をセマフォの値 (semval) に加算する。 さらに、この操作で SEM_UNDO が指定されていた場合は、 システムはこのセマフォのの調整値 (semadj) から値 sem_op を減算する。 この操作は必ず実行でき、 スレッドの停止は起こらない。 呼び出し元プロセスは対象のセマフォ集合を変更する許可がなければならない。

sem_op が 0 の場合、「ゼロまで待つ」操作である。この場合、プロセスは そのセマフォ集合に対する読み込み許可がなければならない。 semval が 0 ならば、操作は直ちに行われる。 semval が 0 でない場合、 sem_flg に IPC_NOWAIT が指定されていれば、 semop() は失敗し、 errno に EAGAIN が設定される (このとき sops に対する操作は全く実行されない)。 sem_flg に IPC_NOWAIT が指定されていない場合、 semzcnt (セマフォ値が 0 になるのを待っているスレッドの数) を 1 増加させて、 以下のいずれかが起こるまでスレッドを停止 (sleep) する。

•   semval が 0 になった: このとき semzcnt の値は 1 減算される。

•   セマフォ集合が削除された: このとき semop() は失敗し、 errno に EIDRM が設定される。

•   呼び出し元スレッドがシグナルを捕獲した: このとき semzcnt の値は 1 減算され、 semop() は失敗し errno に EINTR が設定される。

sem_op が 0 未満の場合、プロセスにはそのセマフォ集合を変更する許可がなければ ならない。 semval が sem_op の絶対値以上の場合は、操作は直ちに実行される: semval から sem_op の絶対値を減算し、さらに、この操作に SEM_UNDO が指定されている場合は、このセマフォの調整値 (semadj) に sem_op の絶対値を加算する。 semval が sem_op の絶対値より小さく、 sem_flg に IPC_NOWAIT が指定された場合は、 semop() は失敗し、 errno に EAGAIN が設定される (このとき sops の操作は全く実行されない)。 semval が sem_op の絶対値より小さく、 IPC_WAIT が指定されていない場合は、 semncnt (このセマフォの値が増加するのを待っているスレッド数のカウンター) を 1 増加させて、以下のいずれかが起こるまでスレッドを停止 (sleep) する。

•   semval が sem_op の絶対値以上になった。この時点で、操作は上述の通り実行される。

•   セマフォ集合がシステムから削除された: このとき semop() は失敗し errno に EIDRM が設定される。

•   呼び出したスレッドがシグナルを捕獲した: このとき semncnt が 1 減算され、 semop() は失敗し errno に EINTR が設定される。

操作が成功した場合、 sops が指す配列によって操作対象となった各セマフォの sempid メンバーには呼び出し元のプロセス ID が設定される。さらに sem_otime に現在時刻が設定される。

  semtimedop()
semtimedop() 関数の振る舞いは semop() と全く同じだが、呼び出し元 スレッドが停止する場合、停止期間の上限が timeout 引き数の指す timespec 構造体で指定された時間となる点だけが異なる (この停止期間は システムクロックの粒度に切り上げられ、カーネルのスケジューリング遅延に より、この停止期間は少しだけ長くなる可能性がある)。 指定した制限時間に達した場合は、 semtimedop() は失敗し、 errno に EAGAIN が設定される (このとき sops の操作は実行されない)。 timeout 引き数が NULL の場合、 semtimedop() 関数の振る舞いは semop() 関数と全く同じになる。 semtimeop() がシグナルにより割り込まれた場合、呼び出しはエラー EINTR で失敗し、 timeout の内容は変更されないままとなる点に注意すること。

返り値
成功した場合、 semop() と semtimedop() は 0 を返す。そうでなければ -1 を返し、 エラーを示す errno を設定する。

エラー
失敗した場合、 errno に以下のどれかが設定される:

E2BIG
    nsops 引き数が SEMOPM より大きい。 SEMOPM は一回のシステムコールで許される操作の最大個数である。

EACCES
    呼び出し元プロセスには指定されたセマフォ操作を行うのに 必要なアクセス許可がなく、 CAP_IPC_OWNER ケーパビリティもない。

EAGAIN
    操作を直ちに処理することができず、かつ sem_flg に IPC_NOWAIT が指定されているか timeout で指定された制限時間が経過した。

EFAULT
    引き数 sops か timeout が指しているアドレスにアクセスできない。

EFBIG
    ある操作で、 sem_num の値が 0 未満か、集合内のセマフォの数以上である。

EIDRM
    セマフォ集合が削除された。

EINTR
    このシステムコールで停止している時にスレッドがシグナルを捕獲した。 single(7) 参照。

EINVAL
    セマフォ集合が存在しないか、 semid が 0 未満であるか、 nsops が正の数でない。

ENOMEM
    ある操作で sem_flg に SEM_UNDO が指定されたが、システムにアンドゥ構造体に割り当てる十分なメモリーがない。

ERANGE
    ある操作で sem_op+semval が SEMVMX より大きい。 SEMVMX は semval の最大値で、その値は実装依存である。

バージョン
semtimedop() は Linux 2.5.52 で初めて登場し、 それからカーネル 2.4.22 にも移植された。 semtimedop() の glibc でのサポートはバージョン 2.3.3 で初めて登場した。

準拠
SVr4, POSIX.1-2001.

注意
Linux や POSIX の全てのバージョンでは、 <sys/types.h> と <sys/ipc.h> のインクルードは必要ない。しかしながら、いくつかの古い実装ではこれらのヘッダーファイルのインクルードが必要であり、 SVID でもこれらのインクルードをするように記載されている。このような古いシステムへの移植性を意図したアプリケーションではこれらのファイルをインクルードする必要があるかもしれない。 あるプロセスの sem_undo 構造体は fork(2) で生成された子プロセスには継承されないが、 execve(2) システムコールの場合は継承される。

semop() はシグナルハンドラーによって中断された後に、 決して自動的に再開することはない。 たとえシグナルハンドラーの設定時に SA_RESTART フラグがセットされていても再開することはない セマフォの調整値 (semadj) は、プロセス毎のセマフォ毎の整数で、 SEM_UNDO フラグを指定して行われた、セマフォに対するすべての操作の合計値を反転したものである。 各プロセスは semadj の値のリストを保持する — リストのそれぞれの値は SEM_UNDO を使って操作が行われた個々のセマフォに対応する。 プロセスが終了する際、 セマフォ毎の semadj の各々の値が対応するセマフォに加算される。 これにより、そのプロセスがそのセマフォに対して行った操作の影響が取り消される (ただし、下記の「バグ」を参照)。 semctl(2) の SETVAL や SETALL を使ってセマフォの値が直接設定された場合、 すべてのプロセスの対応する semadj の値がクリアされる。 clone(2) の CLONE_SYSVSEM フラグを使うと、 複数のプロセスがひとつの semadj リストを共有できる。 詳細は clone(2) を参照。 あるセマフォの semval, sempid, semzcnt, semnct の値はいずれも、適切な操作を指定して semctl(2) を呼び出すことで取得できる。

  セマフォの上限
セマフォ集合のリソースに関する制限のうち、 semop() に影響を及ぼすものを以下に挙げる:

SEMOPM
    一回の semop() で許される操作の最大数 (32)。 (Linux では、この制限値は /proc/sys/kernel/sem の第3フィールドに対応し、読み出しも変更もできる)。

SEMVMX
    semval が取り得る最大値: 実装依存 (32767)。

以下の値に関しては実装依存の制限はない。 終了時の調整 (adjust on exit) の最大値 (SEMAEM)、 システム全体のアンドゥ構造体の最大数 (SEMMNU)、 プロセスあたりのアンドゥ構造体の最大数。

バグ
プロセスが終了する際、プロセスに対応する semadj の集合を使って、 SEM_UNDO フラグ付きで実行された全てのセマフォ操作の影響を取り消す。 これによりある問題が発生する: これらのセマフォの調整を行っていると、 中にはセマフォの値が 0 未満の値にしようとする場合が出てくる。 このような場合、どのように実装するべきか? ひとつの考えられる手法は、全てのセマフォ調整が実行されるまで 停止することである。しかし、この方法ではプロセスの終了が 長時間にわたって停止されることがあるので望ましくない。 しかもどれくらい長時間になるかは分からない。 別の選択肢として、このようなセマフォ調整を完全に無視してしまう方法がある (これはセマフォ操作として IPC_NOWAIT が指定するのと少し似ている)。 Linux は第三の手法を採用している: セマフォの値を出来るだけ (つまり 0 まで) 減少させて、プロセスの終了を直ちに続行できるようにしている。 カーネル 2.6.x (x <= 10) には、ある状況においてセマフォ値が 0 になるのを 待っているスレッドが、セマフォ値が実際に 0 になったときに起床 (wake up) されない、というバグがある。このバグはカーネル 2.6.11 で修正されている。

例
以下の部分的なコードは、 セマフォ 0 の値が 0 になるのを待ってから、 セマフォの値を 1 加算する処理を、 semop() を使ってアトミック (atomically) に行う。
    struct sembuf sops[2];
    int semid;
    /* Code to set semid omitted */
    sops[0].sem_num = 0;        /* Operate on semaphore 0 */
    sops[0].sem_op = 0;         /* Wait for value to equal 0 */
    sops[0].sem_flg = 0;
    sops[1].sem_num = 0;        /* Operate on semaphore 0 */
    sops[1].sem_op = 1;         /* Increment value by one */
    sops[1].sem_flg = 0;
    if (semop(semid, sops, 2) == -1) {
        perror("semop");
        exit(EXIT_FAILURE);
    }

関連項目
clone(2), semctl(2), semget(2), sigaction(2), capabilities(7), sem_overview(7), svipc(7), time(7)

この文書について
この man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。