semget() — Creating & Opening Semaphore Sets

 

semget() — Creating & Opening Semaphore Sets
Chapter 47 · The Linux Programming Interface · Part 2
🔑 Keys & IDs
📦 Semaphore Sets
💻 Code Examples

Function Signature
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

/* Returns: semaphore set identifier (semid) on success, -1 on error */

The semget() call either creates a new semaphore set or obtains the ID of an existing one. Think of it like open() for files — you get a handle (the semid) to work with.

Understanding Each Parameter

Parameter 1: key (type key_t)

The key is like a name for the semaphore set. Two processes can open the same semaphore set by using the same key.

IPC_PRIVATE (value 0)

Always creates a new, private semaphore set with a unique ID. No other process can accidentally find this by key — you must share the semid out-of-band (e.g., via fork inheritance or a file).

ftok()-generated key

Two unrelated processes agree on a pathname and project ID, and both call ftok() to get the same key. They can then both open the same semaphore set by key.

Parameter 2: nsems (number of semaphores in set)

Specifies how many semaphores to put in the set when creating a new set.

When opening an existing set, set nsems to 0 (the system ignores it) or specify the correct count.

Rule: nsems must be ≥ 1 when creating. Must be ≤ the actual count when opening. Typically just pass 0 when opening.

Parameter 3: semflg (flags + permissions)

Combines permission bits (same as file mode bits) with optional creation flags:

Flag Meaning
IPC_CREAT Create the set if it does not exist. If it already exists, just open it.
IPC_CREAT | IPC_EXCL Create the set. If it already exists, fail with EEXIST. Use this to guarantee you are the creator (and thus the initializer).
0600 Owner read+write. These are the minimum permissions needed.
0660 Owner + group read+write. For processes running as the same group.
0666 Everyone read+write. Use when processes run as different users.

ftok() — Generating a Shared Key

When two unrelated processes need to access the same semaphore set, they need a way to agree on the same key. ftok() (“file to key”) generates a reproducible key_t from a pathname + project ID:

#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
/* Returns: key_t value on success, -1 on error */
How it works

Takes the inode number of the file and the lower 8 bits of proj_id, combines them into a unique key. Both processes pass the same pathname + proj_id → both get the same key.

Important rules
  • The pathname must exist
  • Both processes must use the same pathname
  • proj_id must be non-zero
  • Key is NOT guaranteed unique if inode is reused
/* Example: two processes agree on key using ftok() */
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>

#define SEM_KEY_PATH  "/tmp/myapp_sem_key"  /* must exist */
#define SEM_PROJ_ID   'A'                    /* non-zero char */

int get_or_create_semaphore(int nsems) {
    key_t key;
    int semid;

    /* Generate reproducible key from pathname + project char */
    key = ftok(SEM_KEY_PATH, SEM_PROJ_ID);
    if (key == -1) {
        perror("ftok");
        return -1;
    }
    printf("Generated key: 0x%x\n", key);

    /* Try to create. If it already exists, just open it. */
    semid = semget(key, nsems, IPC_CREAT | 0660);
    if (semid == -1) {
        perror("semget");
        return -1;
    }

    return semid;
}

IPC_PRIVATE — Private Semaphores for Related Processes

IPC_PRIVATE creates a truly private semaphore set. The classic use case is parent-child synchronization: the parent creates the semaphore before fork(), and both processes inherit the semid.

#include <sys/sem.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

/* Simple wrapper: subtract 1 (acquire) */
void sem_wait_op(int semid) {
    struct sembuf sop = {0, -1, 0};  /* sem_num=0, sem_op=-1, sem_flg=0 */
    if (semop(semid, &sop, 1) == -1) { perror("semop wait"); exit(1); }
}

/* Simple wrapper: add 1 (release) */
void sem_signal_op(int semid) {
    struct sembuf sop = {0, +1, 0};
    if (semop(semid, &sop, 1) == -1) { perror("semop signal"); exit(1); }
}

int main(void) {
    int semid;
    union semun arg;
    pid_t pid;

    /* Create private semaphore set with 1 semaphore */
    semid = semget(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR);
    if (semid == -1) { perror("semget"); exit(1); }

    /* Initialize semaphore to 0 (child will wait for parent) */
    arg.val = 0;
    if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(1); }

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

    if (pid == 0) {
        /* CHILD: wait until parent signals */
        printf("Child [%d]: waiting for parent to signal...\n", getpid());
        sem_wait_op(semid);   /* blocks here: 0 - 1 would go below 0 */
        printf("Child [%d]: received signal, now proceeding!\n", getpid());
        exit(0);
    } else {
        /* PARENT: do some work, then signal child */
        printf("Parent [%d]: doing work...\n", getpid());
        sleep(2);   /* simulate work */
        printf("Parent [%d]: signaling child now\n", getpid());
        sem_signal_op(semid);  /* add 1 — wakes up child */

        wait(NULL);  /* wait for child to finish */

        /* Clean up: delete the semaphore set */
        if (semctl(semid, 0, IPC_RMID) == -1) { perror("semctl IPC_RMID"); }
        printf("Parent: semaphore deleted\n");
    }

    return 0;
}
Output:
Parent [1001]: doing work…
Child [1002]: waiting for parent to signal…
(2 second pause)
Parent [1001]: signaling child now
Child [1002]: received signal, now proceeding!
Parent: semaphore deleted

IPC_CREAT | IPC_EXCL — Safe Creation Pattern

When multiple unrelated processes start simultaneously, use IPC_CREAT | IPC_EXCL to guarantee only one process creates and initializes the semaphore:

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

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

#define KEY_PATH  "/tmp/sem_demo"
#define KEY_ID    1
#define NUM_SEMS  1

int open_or_create_sem(void) {
    key_t key;
    int semid;
    union semun arg;

    key = ftok(KEY_PATH, KEY_ID);
    if (key == -1) { perror("ftok"); exit(1); }

    /* Try to create exclusively — only one process will succeed */
    semid = semget(key, NUM_SEMS, IPC_CREAT | IPC_EXCL | 0600);

    if (semid != -1) {
        /* WE are the creator — initialize the semaphore */
        printf("[%d] Created semaphore id=%d, initializing...\n", getpid(), semid);
        arg.val = 1;  /* binary semaphore: starts unlocked */
        if (semctl(semid, 0, SETVAL, arg) == -1) {
            perror("semctl SETVAL");
            /* On failure, remove the set we created */
            semctl(semid, 0, IPC_RMID);
            exit(1);
        }
        printf("[%d] Initialized to 1\n", getpid());

    } else if (errno == EEXIST) {
        /* Set already exists — just open it */
        semid = semget(key, 0, 0600);
        if (semid == -1) { perror("semget open"); exit(1); }
        printf("[%d] Opened existing semaphore id=%d\n", getpid(), semid);

    } else {
        perror("semget");
        exit(1);
    }

    return semid;
}

int main(void) {
    int semid = open_or_create_sem();
    printf("[%d] Got semid = %d\n", getpid(), semid);
    return 0;
}
Race condition warning: Even with IPC_CREAT | IPC_EXCL, there is still a tiny window between semget (create) and semctl (initialize) where another process might open the uninitialized set. Chapter 47 covers robust solutions to this using semctl IPC_STAT and the sem_otime field to detect whether initialization has completed.

Opening an Already-Existing Semaphore Set

If the semaphore set was created elsewhere and you just want to get its ID:

/* Open existing semaphore set — no IPC_CREAT flag */
key_t key = ftok("/tmp/shared_app", 42);
if (key == -1) { perror("ftok"); exit(1); }

/* nsems=0 means "don't care about count, just open"
   flags=0 means "no special flags, just get existing" */
int semid = semget(key, 0, 0);
if (semid == -1) {
    if (errno == ENOENT)
        fprintf(stderr, "Semaphore set does not exist yet!\n");
    else
        perror("semget");
    exit(1);
}
printf("Opened semaphore set, semid = %d\n", semid);
errno value Meaning
ENOENT No set exists for this key (and IPC_CREAT not specified)
EEXIST Set already exists but IPC_CREAT | IPC_EXCL was specified
EACCES Set exists but calling process lacks permission
EINVAL nsems is 0 when creating, or nsems > existing count, or nsems > SEMMSL limit
ENOSPC System limit on number of semaphore sets (SEMMNI) reached

Semaphore Permissions — Like File Permissions

System V IPC objects use the same permission model as files (owner, group, other; read, write), but access meanings are slightly different:

Read permission

Allows semctl GETVAL, GETALL, IPC_STAT. Lets you query semaphore values.

Write permission

Allows semop(), semctl SETVAL, SETALL, IPC_RMID. Lets you modify semaphore values.

/* Common permission combinations */

/* Owner only — single-user applications */
semid = semget(key, 1, IPC_CREAT | 0600);

/* Owner + group — processes in same group (most common for daemons) */
semid = semget(key, 1, IPC_CREAT | 0660);

/* Everyone — when processes may run as different users */
semid = semget(key, 1, IPC_CREAT | 0666);

/* Check permissions with ipcs command:
   $ ipcs -s
   ------ Semaphore Arrays --------
   key        semid      owner      perms      nsems
   0x00000000 0          ravi       600        1
   0x00010203 1          ravi       660        3
*/

Listing & Removing Semaphores from the Shell

System V semaphores persist until explicitly deleted or the system reboots. Stale semaphores can cause problems in testing. Use these shell commands:

# List all semaphore sets
$ ipcs -s

# Show detailed info about a specific semid
$ ipcs -s -i 98307

# Remove a semaphore set by semid
$ ipcrm -s 98307

# Remove all semaphore sets (careful!)
$ ipcs -s | awk 'NR>2 { print $2 }' | xargs -I{} ipcrm -s {}
Common debugging tip: If your program crashes before calling semctl(IPC_RMID), the semaphore set stays in the kernel. Running your program again may find a stale set with wrong values. Always use ipcs -s and ipcrm -s to clean up during development.

Complete Example: Multi-Semaphore Set Creation

Creating a set with 3 semaphores to protect different resources:

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

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

/* Semaphore indices — give them meaningful names */
#define SEM_MUTEX    0    /* protects critical section (init=1) */
#define SEM_PRODUCER 1    /* counts items to consume (init=0) */
#define SEM_CONSUMER 2    /* counts free slots (init=BUFFER_SIZE) */
#define NUM_SEMS     3
#define BUFFER_SIZE  10

int create_sem_set(key_t key) {
    int semid;
    union semun arg;
    unsigned short init_vals[NUM_SEMS];

    /* Create the set */
    semid = semget(key, NUM_SEMS, IPC_CREAT | IPC_EXCL | 0660);
    if (semid == -1) {
        if (errno == EEXIST) {
            /* Already exists — just open */
            return semget(key, 0, 0660);
        }
        perror("semget");
        return -1;
    }

    /* Initialize all semaphores at once using SETALL */
    init_vals[SEM_MUTEX]    = 1;           /* unlocked */
    init_vals[SEM_PRODUCER] = 0;           /* no items yet */
    init_vals[SEM_CONSUMER] = BUFFER_SIZE; /* all slots free */
    arg.array = init_vals;

    if (semctl(semid, 0, SETALL, arg) == -1) {
        perror("semctl SETALL");
        semctl(semid, 0, IPC_RMID);  /* cleanup on failure */
        return -1;
    }

    printf("Created semaphore set: semid=%d\n", semid);
    printf("  sem[MUTEX]    = %d\n", init_vals[SEM_MUTEX]);
    printf("  sem[PRODUCER] = %d\n", init_vals[SEM_PRODUCER]);
    printf("  sem[CONSUMER] = %d\n", init_vals[SEM_CONSUMER]);

    return semid;
}

int main(void) {
    key_t key;
    int semid;

    /* Create key file if it doesn't exist */
    system("touch /tmp/sem_multitest");

    key = ftok("/tmp/sem_multitest", 99);
    if (key == -1) { perror("ftok"); exit(1); }

    semid = create_sem_set(key);
    if (semid == -1) { exit(1); }

    printf("\nSemaphore set ready. semid = %d\n", semid);
    printf("Use 'ipcs -s' to verify, 'ipcrm -s %d' to delete\n", semid);

    return 0;
}

Interview Questions — semget()
Q1: What is the difference between IPC_PRIVATE and a key generated by ftok()?
IPC_PRIVATE (value 0) always creates a brand-new semaphore set and gives it a unique ID. No other process can look it up by key — the semid must be communicated out-of-band (e.g., by inheritance via fork, or written to a file). ftok() generates a deterministic key from a pathname and project ID, so any unrelated process with access to the same file can generate the same key and use semget to open the same set. Use IPC_PRIVATE for parent-child; use ftok() for unrelated processes.
Q2: What does semget() return, and when does it return -1?
On success it returns a non-negative integer called the semaphore set identifier (semid). It returns -1 on error: if the key maps to no existing set (errno=ENOENT), if the set already exists with IPC_EXCL (EEXIST), if permissions are wrong (EACCES), if nsems is invalid (EINVAL), or if system limits are exceeded (ENOSPC).
Q3: What happens to a semaphore set if the creating process exits without calling IPC_RMID?
The semaphore set persists in the kernel. Unlike POSIX semaphores with sem_open(), System V IPC objects are kernel-persistent — they survive process termination and stay until explicitly deleted with semctl(IPC_RMID) or the system reboots. This is a common cause of resource leaks and must be handled carefully, especially when programs crash.
Q4: Why would you use IPC_CREAT | IPC_EXCL together?
IPC_CREAT alone creates if absent, or opens if present. This is ambiguous — you cannot tell whether you are the creator (and should initialize) or an opener (set already initialized). IPC_CREAT | IPC_EXCL succeeds only for the creator (fails with EEXIST otherwise). The process that succeeds knows it is the creator and is responsible for initialization with semctl.
Q5: What does the nsems argument mean, and what value should you pass when opening an existing set?
nsems specifies how many semaphores to create in the set when creating a new one. It must be ≥ 1 for creation. When opening an existing set (no IPC_CREAT, or IPC_CREAT without IPC_EXCL for an existing set), nsems should be 0 (ignored by the kernel) or may be set to the known count. Specifying a value larger than the existing set’s count causes EINVAL.

Leave a Reply

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