SysV IPC

semop#

Perform one or more System V semaphore operations atomically.

semop is the workhorse of SysV semaphore use: it takes a semaphore set identifier (from semget) and a packed list of operations, then asks the kernel to apply them all or none. Each operation names a single semaphore within the set, an amount to add to it (typically -1 to wait, +1 to signal), and a flag word. The kernel blocks the caller until every requested operation can proceed, then commits them as one unit. This is what makes semop usable as a mutual-exclusion or counting-resource primitive between unrelated processes.

Synopsis#

semop $semid, $opstring

What you get back#

True on success, false on error with $! set. The truth value is the only result — semop communicates outcome by the side effect on the semaphore set, not through its return. Check it on every call:

semop($semid, $ops)
    or die "semop failed: $!";

A false return means no operation in OPSTRING was applied; SysV semantics guarantee all-or-nothing.

Global state it touches#

Sets $! on failure, with the usual errno values from semop(2): EAGAIN when IPC_NOWAIT was set and the operation would block, EIDRM when the semaphore set was removed while waiting, EINTR when a signal interrupted the wait, EINVAL for a bad semaphore number or identifier, EACCES for a permission mismatch against the set’s mode.

The operation structure#

OPSTRING is a concatenation of fixed-width records. Each record is three native-width short integers and is produced with pack:

pack("s!3", $semnum, $semop, $semflag)
  • $semnum — the index of the target semaphore within the set (0 through nsems-1, where nsems is the size passed to semget).

  • $semop — the amount to add to the semaphore’s value. Negative values wait until the semaphore is at least |$semop| before subtracting; positive values add unconditionally and wake waiters; zero waits until the semaphore reaches zero.

  • $semflag — bitwise OR of IPC_NOWAIT (fail with EAGAIN instead of blocking) and SEM_UNDO (have the kernel reverse the operation if the process exits without undoing it — essential for lock-like uses where a crash must not leave the semaphore held).

The length of OPSTRING implies the number of operations: the kernel processes length($opstring) / sizeof(struct sembuf) records. Passing two records packed end-to-end asks for two operations applied atomically.

The s! in the template is the native short, which matches the sembuf layout on Linux. Plain s is network-order signed 16-bit and will silently mis-pack on any platform where sizeof(short) != 2 or where endianness differs from network order.

Examples#

Wait on (decrement by 1) semaphore 0 of the set in $semid:

my $op = pack("s!3", 0, -1, 0);
semop($semid, $op)
    or die "wait on semaphore failed: $!";

Signal (increment by 1) the same semaphore:

my $op = pack("s!3", 0, 1, 0);
semop($semid, $op)
    or die "signal failed: $!";

Try to acquire without blocking — fail fast if someone else holds it:

use POSIX qw(EAGAIN);
my $op = pack("s!3", 0, -1, IPC_NOWAIT);
unless (semop($semid, $op)) {
    die "semop: $!" unless $! == EAGAIN;
    # contended; caller decides what to do
}

Acquire semaphore 0 with automatic release on process exit — the pattern for crash-safe mutual exclusion:

my $op = pack("s!3", 0, -1, SEM_UNDO);
semop($semid, $op) or die "lock: $!";
# critical section
my $release = pack("s!3", 0, 1, SEM_UNDO);
semop($semid, $release) or die "unlock: $!";

Two operations applied atomically — wait for semaphore 0 and semaphore 1 together, releasing neither if either would block:

my $ops = pack("s!3", 0, -1, 0)
        . pack("s!3", 1, -1, 0);
semop($semid, $ops) or die "paired wait: $!";

Wait for semaphore 0 to reach zero — the “barrier” form, where $semop is zero rather than negative:

my $op = pack("s!3", 0, 0, 0);
semop($semid, $op) or die "barrier: $!";

Edge cases#

  • Short OPSTRING: the length must be an exact multiple of the sembuf record size. A truncated or padded string gives EINVAL. Always build the string from pack calls, never by hand.

  • Native width matters: s!3 not s3. A script written with plain s works on a 16-bit-short host by accident and breaks elsewhere.

  • Zero-length OPSTRING: asks for zero operations. The kernel accepts this and returns success without side effects — rarely what you want. Guard the call with return unless length $ops.

  • Signal interruption: a blocked semop returning with $! set to EINTR is not a bug — retry unless you installed a handler that wants to abort. Perl’s default $SIG{*} handling does not automatically restart the syscall.

  • SEM_UNDO with matched pairs: the kernel tracks the cumulative adjustment per semaphore per process. Acquiring twice with SEM_UNDO then releasing once leaves an outstanding -1 to be undone on exit. Pair every -1 with a matching +1 in normal flow; SEM_UNDO is insurance against crashes, not a substitute for releasing.

  • Removed set while waiting: if another process calls semctl with IPC_RMID, every blocked semop returns false with $! set to EIDRM. The semaphore identifier is invalid from that point on.

  • Permissions: semop checks alter-permission on the set. A process that can semget a read-only handle still cannot semop it.

  • Large operation counts: the kernel caps the number of operations per call at SEMOPM (typically 32). Building an OPSTRING with more records fails with E2BIG.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • semget — obtain the identifier you pass as the first argument; the two are always used together

  • semctl — set or query semaphore values, and remove the set when you’re done with it

  • pack — build each sembuf record; s!3 is the template you want

  • msgsnd — the message-queue counterpart when you need to pass data rather than signal availability

  • shmget — the shared-memory counterpart; semaphores are typically used to guard access to a SysV shared-memory segment

  • IPC::SysV — exports the IPC_NOWAIT, SEM_UNDO, and IPC_RMID constants used in $semflag and in semctl commands

  • IPC::Semaphore — object wrapper over semget / semop / semctl when a procedural interface feels too bare