IPC Synchronization
semget()
Intermediate
A semaphore is a kernel-maintained integer used to coordinate access to shared resources between processes or threads. Unlike mutexes which are purely binary (locked/unlocked), semaphores can hold non-negative integer values and support counting semantics.
The core idea: before accessing a shared resource, a process decrements (waits on) the semaphore. If the value would go negative, the process blocks. When done, the process increments (posts) the semaphore, potentially waking up a waiting process.
System V semaphores were introduced with UNIX System V and are part of the “System V IPC” trio — semaphores, message queues, and shared memory. They are identified by integer keys, not file descriptors.
| Process A wants shared memory |
Semaphore Value = 1 decrement → value = 0 → OK, enter |
Shared Resource e.g. shared memory |
| Process B tries to enter — BLOCKS |
Semaphore Value = 0 decrement → would be -1 → BLOCK |
Currently used by Process A |
System V semaphores are allocated in sets. A semaphore set contains one or more semaphores. Each semaphore within the set is an independent non-negative integer. The entire set is treated as a single object — it has one identifier, one set of permissions, and one creation/deletion lifecycle.
When you create a semaphore set, you specify how many semaphores it contains. You then operate on individual semaphores by their index (0-based) within the set.
| semid (e.g. 5) | Semaphore Set (nsems = 4) | |||
| sem[0] val=1 |
sem[1] val=0 |
sem[2] val=3 |
sem[3] val=0 |
|
Most applications only need a single semaphore, but the set structure is always present.
semget() is the first call you make. It either creates a new semaphore set or opens an existing one.
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
/* Returns semaphore set identifier (semid) on success, -1 on error */
Parameters:
| Parameter | Description |
|---|---|
| key | IPC key — identifies the semaphore set. Use ftok() to generate or IPC_PRIVATE for private sets. |
| nsems | Number of semaphores in the set. Ignored when opening an existing set (pass 0). |
| semflg | Flags + permission bits. Common: IPC_CREAT, IPC_EXCL, e.g. IPC_CREAT | 0660 |
Flag combinations and their meaning:
| Flags | Behaviour |
|---|---|
| 0 (no flags) | Open existing set; fail (ENOENT) if not found |
| IPC_CREAT | Create if not exists; open if exists |
| IPC_CREAT | IPC_EXCL | Create new; fail (EEXIST) if already exists |
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <errno.h>
int main(void)
{
key_t key;
int semid;
/* Generate a key from a file path + project id */
key = ftok("/tmp/mysem_file", 42);
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
/*
* Create a semaphore set with 2 semaphores.
* IPC_CREAT | 0600 : create if not exists, owner r/w only.
*/
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0600);
if (semid == -1) {
if (errno == EEXIST) {
/* Already exists — open it instead */
semid = semget(key, 0, 0);
if (semid == -1) { perror("semget open"); exit(EXIT_FAILURE); }
printf("Opened existing semaphore set, id=%d\n", semid);
} else {
perror("semget create");
exit(EXIT_FAILURE);
}
} else {
printf("Created new semaphore set, id=%d\n", semid);
}
return 0;
}
IPC_PRIVATE always creates a new semaphore set regardless of any existing sets. It is useful when a process wants a semaphore shared only with its children (the children inherit the semid across fork).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/wait.h>
int main(void)
{
int semid;
pid_t pid;
/* IPC_PRIVATE creates a fresh set; nsems=1, one semaphore */
semid = semget(IPC_PRIVATE, 1, 0600);
if (semid == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
printf("Private semid = %d\n", semid);
pid = fork();
if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); }
if (pid == 0) {
/* Child: same semid is valid here after fork */
printf("Child: semid = %d (inherited)\n", semid);
exit(0);
}
wait(NULL);
/* Parent must clean up — kernel does NOT auto-delete */
if (semctl(semid, 0, IPC_RMID) == -1)
perror("semctl IPC_RMID");
else
printf("Semaphore set deleted\n");
return 0;
}
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
/* Returns IPC key on success, -1 on error */
ftok() generates a key by combining the inode number and device of pathname with the low 8 bits of proj_id. The file must exist and be accessible. Multiple unrelated processes can arrive at the same key by agreeing on the same pathname and proj_id.
/* Both processes use the same key: */
key_t key = ftok("/usr/local/myapp/lockfile", 1);
/*
* WARNING: if the file is deleted and recreated, its inode number
* changes, so ftok() will return a different key.
* Use a stable, long-lived file for the pathname.
*/
Like files, semaphore sets have ownership (UID/GID) and permission bits. However, there is no execute bit — only read and write/alter bits are meaningful.
| Permission | Octal | Meaning |
|---|---|---|
| Owner read | 0400 | Can read semaphore values (semctl GETVAL etc.) |
| Owner write/alter | 0200 | Can perform semop() and semctl() modifications |
| Group read | 0040 | Group members can read |
| Group write | 0020 | Group members can alter |
| Other read | 0004 | Others can read |
| Other write | 0002 | Others can alter |
/* Common permission patterns */
semget(key, 1, IPC_CREAT | 0600); /* only owner can use */
semget(key, 1, IPC_CREAT | 0660); /* owner + group */
semget(key, 1, IPC_CREAT | 0666); /* everyone — use carefully */
Q1. What is the difference between a semaphore and a mutex?
A mutex is a binary lock owned by the thread that locked it — only that thread can unlock it. A semaphore is a kernel counter with no ownership. It can be incremented by any process/thread and supports values greater than 1 (counting semaphore). A binary semaphore (value 0 or 1) behaves similarly to a mutex but without ownership semantics.
Q2. Why does semget() operate on sets of semaphores rather than individual ones?
The System V design allows atomic operations on multiple semaphores simultaneously using a single semop() call. This prevents deadlocks in situations where a process must acquire multiple resources at once — if all increments/decrements happen atomically, there is no window for partial acquisition that leads to circular waiting.
Q3. What is IPC_PRIVATE and when should you use it?
IPC_PRIVATE is a special key value (0) that always creates a brand-new semaphore set. It is appropriate when the semaphore will be shared only between related processes (parent and children) because the semid is passed via inheritance through fork(), not through a shared key.
Q4. What happens to a System V semaphore set when all processes using it exit?
Nothing — it persists in the kernel until explicitly deleted with semctl(semid, 0, IPC_RMID) or the system reboots. This is a major difference from POSIX semaphores and file-based mechanisms, and is one of the well-known disadvantages of System V IPC.
Q5. What is the role of ftok() and what are its limitations?
ftok() derives an IPC key from a file’s inode number and a project ID byte. Its main limitation is that if the file is deleted and recreated, the inode changes and ftok() returns a different key — causing processes to fail to find each other’s semaphore sets. Also, two different path/project_id pairs can theoretically collide to produce the same key (very rare but possible).
Q6. What does the nsems parameter mean in semget(), and what value should you pass when opening an existing set?
nsems specifies how many semaphores to create in the new set. When opening an existing set (not creating), pass 0 — the value is ignored by the kernel in that case.
Learn how to wait on, signal, and zero-test semaphores using the semop() system call.
