Unnamed Semaphores POSIX Semaphores

 

Unnamed Semaphores
POSIX Semaphores Chapter 53 — File 3 of 5

What is an Unnamed Semaphore?

An unnamed semaphore is simply a sem_t variable — it has no name and is not visible in the filesystem. It is initialized directly in memory using sem_init(). Because it lives in memory, sharing depends entirely on where that memory is located.

Two modes of sharing are possible. In thread-shared mode, the sem_t is a global or heap variable and all threads in the same process can access it. In process-shared mode, the sem_t is placed in shared memory (e.g., POSIX shared memory or a mmap’d file) so multiple processes can access it.

sem_init() — Initialize an Unnamed Semaphore
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

/* Returns 0 on success, -1 on error */
Parameter Meaning
sem Pointer to the sem_t variable to initialize
pshared 0 = thread-shared (within same process). Non-zero = process-shared (must be in shared memory)
value Initial value of the semaphore (1 for binary, N for counting semaphore)
Key Rules from SUSv3/SUSv4
  • Initializing an already-initialized unnamed semaphore causes undefined behavior. Only one process or thread must call sem_init().
  • Operations must always be on the original sem_t variable. Never copy it and operate on the copy.
  • There are no permission settings on unnamed semaphores (no mode argument). Access is controlled by the permissions on the shared memory region.
  • The NPTL (Native POSIX Thread Library on Linux) implementation ignores pshared since no special action is needed. Still, always pass the correct value for portability.
  • SUSv3 left the return value on success undefined; SUSv4 clarified it returns 0 on success.

Thread-Shared vs Process-Shared Unnamed Semaphores

pshared = 0: Thread-Shared

Process (Single)
Thread 1
Thread 2
Thread 3
↑↑↑
sem_t sem (global/stack)

sem_t is a global variable or on heap. All threads can access it directly since they share the process address space.

pshared != 0: Process-Shared
Process A
↓↓
Shared Memory Region
sem_t sem (inside shm)
↑↑
Process B

sem_t must live in shared memory (e.g., shm_open + mmap or MAP_SHARED mmap). Both processes mmap the same region and get access to the same sem_t.

sem_destroy() — Destroy an Unnamed Semaphore
#include <semaphore.h>

int sem_destroy(sem_t *sem);

/* Returns 0 on success, -1 on error */

Destroys the unnamed semaphore pointed to by sem. After destruction, the memory can be reused or the sem_t can be reinitialized with sem_init().

When to call sem_destroy()
  • Before freeing the memory containing the sem_t
  • If on stack: before the function returns
  • If in shared memory: after all processes stop using it, before shm_unlink()
What NOT to do
  • Do NOT destroy a semaphore while another thread/process is waiting on it
  • Do NOT destroy and then use the semaphore without reinitializing
  • Omitting sem_destroy() may cause resource leaks on some implementations

Example 1: Thread-Shared Unnamed Semaphore (from TLPI Listing 53-6)

Two threads increment a global counter. An unnamed semaphore (initialized to 1) acts as a binary mutex protecting the critical section. This is the canonical example from TLPI.

/* thread_incr_psem.c
 * Two threads race to increment a global variable.
 * An unnamed semaphore (binary) protects the critical section.
 *
 * Compile: gcc -o thread_incr_psem thread_incr_psem.c -lpthread
 * Usage:   ./thread_incr_psem [num-loops]
 * Default: 10000000 loops per thread
 */
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

static int glob = 0;
static sem_t sem;   /* Unnamed, thread-shared semaphore */

static void *
threadFunc(void *arg)
{
    int loops = *((int *) arg);
    int loc, j;

    for (j = 0; j < loops; j++) {
        /* Acquire semaphore (decrement: 1 -> 0, blocks if already 0) */
        if (sem_wait(&sem) == -1) {
            perror("sem_wait");
            return NULL;
        }

        /* --- Critical section --- */
        loc = glob;  /* Read global into local */
        loc++;       /* Increment local copy */
        glob = loc;  /* Write back */
        /* --- End critical section --- */

        /* Release semaphore (increment: 0 -> 1) */
        if (sem_post(&sem) == -1) {
            perror("sem_post");
            return NULL;
        }
    }
    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t t1, t2;
    int loops, s;

    loops = (argc > 1) ? atoi(argv[1]) : 10000000;

    /* Initialize unnamed semaphore: pshared=0 (thread-shared), value=1 */
    if (sem_init(&sem, 0, 1) == -1) {
        perror("sem_init");
        exit(EXIT_FAILURE);
    }

    /* Create two competing threads */
    s = pthread_create(&t1, NULL, threadFunc, &loops);
    if (s != 0) { fprintf(stderr, "pthread_create t1 failed\n"); exit(1); }

    s = pthread_create(&t2, NULL, threadFunc, &loops);
    if (s != 0) { fprintf(stderr, "pthread_create t2 failed\n"); exit(1); }

    /* Wait for both threads to finish */
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    /* Without the semaphore, glob would be less than loops*2 due to races */
    printf("glob = %d (expected: %d)\n", glob, loops * 2);

    sem_destroy(&sem);
    exit(EXIT_SUCCESS);
}
Why three lines for increment?

The pattern loc = glob; loc++; glob = loc; (instead of just glob++) intentionally exposes the race condition for demonstration. Without the semaphore, thread A could read glob=5, get preempted, thread B reads glob=5 too, both write back 6 instead of 7. The semaphore prevents this interleaving.

Example 2: Process-Shared Unnamed Semaphore via POSIX Shared Memory

For process-sharing, the sem_t must live in a shared memory region that both processes can map. Here is a self-contained example using a parent-child fork pattern.

/* process_shared_sem.c
 * Parent and child share a semaphore placed in anonymous shared memory.
 * The child waits for the parent to post before continuing.
 *
 * Compile: gcc -o proc_sem process_shared_sem.c -lpthread
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/wait.h>

int main(void)
{
    sem_t *sem;
    pid_t pid;

    /*
     * Create anonymous shared memory region using mmap.
     * MAP_SHARED | MAP_ANONYMOUS: shared between parent and child,
     * not backed by a file.
     */
    sem = mmap(NULL, sizeof(sem_t),
               PROT_READ | PROT_WRITE,
               MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (sem == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    /*
     * Initialize semaphore in shared memory.
     * pshared = 1 means process-shared.
     * Initial value = 0: child will block until parent posts.
     */
    if (sem_init(sem, 1, 0) == -1) {
        perror("sem_init");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        /* Child process: wait for parent to signal */
        printf("Child (PID %d): waiting for parent...\n", getpid());
        if (sem_wait(sem) == -1) {
            perror("sem_wait");
            exit(EXIT_FAILURE);
        }
        printf("Child (PID %d): got signal, proceeding!\n", getpid());
        exit(EXIT_SUCCESS);

    } else {
        /* Parent process: do some work, then signal child */
        printf("Parent (PID %d): doing work for 2 seconds...\n", getpid());
        sleep(2);
        printf("Parent (PID %d): posting semaphore\n", getpid());
        if (sem_post(sem) == -1) {
            perror("sem_post");
            exit(EXIT_FAILURE);
        }
        wait(NULL); /* Wait for child to finish */
    }

    /* Cleanup: destroy before unmapping */
    sem_destroy(sem);
    munmap(sem, sizeof(sem_t));

    printf("Parent: done.\n");
    return 0;
}

Example 3: Producer-Consumer Using Two Unnamed Semaphores

Classic producer-consumer with a bounded buffer of size 1. Two semaphores signal “empty slot available” and “item available”.

/* prod_cons_sem.c
 * Single producer, single consumer sharing a buffer of size 1.
 * Two semaphores:
 *   empty: starts at 1 (buffer empty, producer can write)
 *   full:  starts at 0 (no item yet, consumer must wait)
 *
 * Compile: gcc -o prod_cons prod_cons_sem.c -lpthread
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

#define NUM_ITEMS 5

static sem_t empty;  /* 1 = buffer has space */
static sem_t full;   /* 1 = buffer has item  */
static int   buffer; /* Shared buffer (size 1) */

static void *producer(void *arg) {
    int i;
    for (i = 1; i <= NUM_ITEMS; i++) {
        sem_wait(&empty);  /* Wait for empty slot */
        buffer = i;
        printf("Produced: %d\n", buffer);
        sem_post(&full);   /* Signal item is ready */
        sleep(1);
    }
    return NULL;
}

static void *consumer(void *arg) {
    int i, item;
    for (i = 0; i < NUM_ITEMS; i++) {
        sem_wait(&full);   /* Wait for an item */
        item = buffer;
        printf("Consumed: %d\n", item);
        sem_post(&empty);  /* Signal buffer is free again */
    }
    return NULL;
}

int main(void) {
    pthread_t prod_tid, cons_tid;

    /* empty=1: producer can start immediately */
    /* full=0:  consumer must wait for first item */
    sem_init(&empty, 0, 1);
    sem_init(&full,  0, 0);

    pthread_create(&prod_tid, NULL, producer, NULL);
    pthread_create(&cons_tid, NULL, consumer, NULL);

    pthread_join(prod_tid, NULL);
    pthread_join(cons_tid, NULL);

    sem_destroy(&empty);
    sem_destroy(&full);
    return 0;
}

sem_init() Decision Guide

Who will use the semaphore?
Threads in the SAME process

pshared = 0

Place sem_t anywhere: global variable, struct member, heap allocation

sem_init(&sem, 0, value);

Multiple PROCESSES

pshared != 0

Place sem_t in shared memory: mmap(MAP_SHARED), POSIX shm, or System V shm

sem_init(shm_ptr, 1, value);

Interview Questions — Unnamed Semaphores
Q1. What does the pshared argument of sem_init() mean?

If pshared is 0, the semaphore is shared only among threads of the same process. If pshared is non-zero, the semaphore can be shared between processes, but it must be placed in a shared memory region (e.g., from mmap with MAP_SHARED, or a POSIX shared memory object).

Q2. What happens if you call sem_init() twice on the same semaphore without calling sem_destroy() in between?

This results in undefined behavior per SUSv3/SUSv4. Your application must be designed so that sem_init() is called exactly once per semaphore, typically at initialization time. To reinitialize, call sem_destroy() first, then sem_init() again.

Q3. How do you share an unnamed semaphore between parent and child processes created with fork()?

Create a shared memory region using mmap() with MAP_SHARED | MAP_ANONYMOUS before calling fork(). Place the sem_t variable at the beginning of this region and initialize it with sem_init(…, 1, …). After fork(), both parent and child have the same mapping and share the same semaphore.

Q4. Why does NPTL ignore the pshared argument?

NPTL (Native POSIX Thread Library — the Linux thread implementation) ignores pshared because its sem_t implementation works the same way regardless. The semaphore is accessible by anyone who has the address of the sem_t. Portability still requires setting the correct value, since other platforms may enforce the distinction.

Q5. When should you call sem_destroy()?

Before the memory holding the sem_t is released. For a local variable: before the function returns. For heap memory: before free(). For shared memory: after all processes have stopped using it and before shm_unlink(). While some implementations won’t crash if you skip it, others leak resources — so always call it for portable code.

Q6. Can you copy a sem_t variable and use both the original and the copy?

No. Both SUSv3 and SUSv4 state that the result of using a copy of a sem_t is undefined. All operations must be performed on the original variable whose address was passed to sem_init(). This rule also applies to named semaphores — never copy the sem_t pointer returned by sem_open.

Next: Semaphore Comparisons

Learn how POSIX semaphores compare to System V semaphores and Pthreads mutexes.

← Named Semaphores File 4: Comparisons →

Leave a Reply

Your email address will not be published. Required fields are marked *