System V IPC Identifier Algorithm

 

System V IPC Identifier Algorithm
Chapter 45, Section 45.5 | How the Kernel Generates Unique IPC Identifiers
SEQ_MULTIPLIER
= 32,768
IPCMNI
Max IPC objects
EINVAL / EIDRM
Invalid ID errors
Key Terms
ipc_ids structure entries array seq field SEQ_MULTIPLIER IPCMNI identifier formula EINVAL EIDRM index calculation

Why Does the Identifier Algorithm Matter?

When a server restarts and recreates an IPC object with the same key, why does the new object get a different identifier? This is not a coincidence — the kernel uses a deliberate algorithm designed to make identifier reuse very unlikely in the short term.

Understanding this algorithm helps you understand: how identifiers are generated, why stale identifiers are detectable, how to quickly find an object from its identifier, and the limits on how many IPC objects can exist simultaneously.

Kernel Internal Data Structures

For each IPC mechanism, the kernel maintains an ipc_ids structure. This is a global structure (one per mechanism) that keeps track of all instances of that mechanism.

Kernel ipc_ids Structure for Semaphores (Figure 45-1 from TLPI)

ipc_ids structure (sem_ids)
seq = 10
max_id = 3
in_use = 2
size = 128
entries → (pointer to array)

entries[] array
0
NULL (empty)
1
key=0x4d0731db, seq=9
2
key=0x4b079002, seq=5
3
NULL (empty)
Key fields: seq = global counter incremented each time any object is created. entries[i] = pointer to semid_ds (or NULL if slot is free). max_id = highest currently-used index. in_use = count of active objects.

The get() Call Algorithm — Step by Step

When you call msgget(), semget(), or shmget(), here’s what the kernel does:

Step 1: Search for the key
Scan the entries[] array for a structure whose __key matches the provided key.
1a. Key not found + IPC_CREAT not set: return ENOENT error
1b. Key found + both IPC_CREAT and IPC_EXCL set: return EEXIST error
1c. Key found + no EEXIST conflict: skip to step 3 (return existing object’s identifier)
Step 2: Create new object (if IPC_CREAT, key not found)
Allocate a new data structure, find a free slot in entries[], then:
2a. Copy the key into xxx_perm.__key
2b. Copy current ipc_ids.seq into xxx_perm.__seq, then increment ipc_ids.seq
Step 3: Calculate and return identifier
identifier = index + xxx_perm.__seq * SEQ_MULTIPLIER

The Identifier Formula
identifier = index + (xxx_perm.__seq × SEQ_MULTIPLIER)

Where:

  • index = position of this object’s data structure in the entries[] array
  • xxx_perm.__seq = sequence number saved at creation time (from ipc_ids.seq)
  • SEQ_MULTIPLIER = 32,768 (defined as IPCMNI in the Linux kernel)
Worked Example from Figure 45-1
Object at entries[2]:
key = 0x4b079002
seq = 5

identifier = 2 + (5 × 32768)
= 2 + 163840
= 163,842

Object at entries[1]:
key = 0x4d0731db
seq = 9

identifier = 1 + (9 × 32768)
= 1 + 294912
= 294,913

Reverse operation (identifier → index):
index = identifier % SEQ_MULTIPLIER
This is used by the kernel to quickly look up the data structure for a given identifier.

Why New Objects (Almost) Never Get the Same Identifier

When a new IPC object is created (even with the same key), it gets a different __seq value — because ipc_ids.seq is incremented for every object creation of that type. So the formula produces a different result.

The only way two objects at the same index could get the same identifier: 65,535 objects must be created in between (because seq wraps to 0 after INT_MAX / IPCMNI = 65,535). In practice, this almost never happens.

Why Same Key → Different ID on Restart
Creation 1
seq=5 at index=2 → ID = 2 + 5×32768 = 163,842
Delete
Object at index=2 freed. seq in ipc_ids has been incremented to 6.
Creation 2
Same key, same index=2 (slot reused), but seq=6 → ID = 2 + 6×32768 = 196,610
Clients holding ID=163,842 will receive EIDRM when they try to use it.

Coding Example 1 — Verifying the Identifier Formula
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>

#define SEQ_MULTIPLIER 32768   /* IPCMNI from kernel source */

int main(void) {
    int id1, id2, id3;
    int index1, index2, index3;
    int seq1, seq2, seq3;

    /* Create three semaphore sets with unique keys */
    id1 = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    id2 = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    id3 = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);

    if (id1 == -1 || id2 == -1 || id3 == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    printf("IDs:     id1=%d  id2=%d  id3=%d\n", id1, id2, id3);

    /* Reverse-engineer the index and sequence from the identifier */
    index1 = id1 % SEQ_MULTIPLIER;
    seq1   = id1 / SEQ_MULTIPLIER;

    index2 = id2 % SEQ_MULTIPLIER;
    seq2   = id2 / SEQ_MULTIPLIER;

    index3 = id3 % SEQ_MULTIPLIER;
    seq3   = id3 / SEQ_MULTIPLIER;

    printf("\nDerived from formula (id = index + seq * 32768):\n");
    printf("id1=%d → index=%d, seq=%d  (verify: %d + %d*32768 = %d)\n",
           id1, index1, seq1, index1, seq1, index1 + seq1 * SEQ_MULTIPLIER);
    printf("id2=%d → index=%d, seq=%d  (verify: %d + %d*32768 = %d)\n",
           id2, index2, seq2, index2, seq2, index2 + seq2 * SEQ_MULTIPLIER);
    printf("id3=%d → index=%d, seq=%d  (verify: %d + %d*32768 = %d)\n",
           id3, index3, seq3, index3, seq3, index3 + seq3 * SEQ_MULTIPLIER);

    /* Cleanup */
    semctl(id1, 0, IPC_RMID);
    semctl(id2, 0, IPC_RMID);
    semctl(id3, 0, IPC_RMID);

    return 0;
}
Run this and observe: each new IPC object gets a higher seq value. Notice that after deleting and recreating, the ID changes because seq incremented.

Coding Example 2 — EINVAL vs EIDRM Errors
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(void) {
    int id, fake_id;
    struct sembuf op = { .sem_num=0, .sem_op=1, .sem_flg=0 };

    /* Create a semaphore */
    id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    if (id == -1) { perror("semget"); exit(EXIT_FAILURE); }
    printf("Created semaphore ID = %d\n", id);

    /* Case 1: Use a completely invalid (never-existed) identifier */
    fake_id = 999999999;
    op.sem_num = 0; op.sem_op = 1;
    if (semop(fake_id, &op, 1) == -1) {
        if (errno == EINVAL)
            printf("EINVAL: ID %d has no corresponding entry (never existed)\n", fake_id);
        else
            perror("semop");
    }

    /* Case 2: Delete the semaphore, then try to use the old ID */
    semctl(id, 0, IPC_RMID);
    printf("Semaphore %d deleted.\n", id);

    if (semop(id, &op, 1) == -1) {
        if (errno == EINVAL)
            printf("EINVAL: slot for ID %d is now empty\n", id);
        else if (errno == EIDRM)
            printf("EIDRM: ID %d was removed (slot reused with different seq)\n", id);
        else
            perror("semop");
    }

    return 0;
}
EINVAL vs EIDRM:

  • EINVAL — the entries[] slot is completely empty (the index portion of the ID points to an unused slot)
  • EIDRM — the slot is occupied, but the sequence number doesn’t match the ID (old object was deleted and slot reused for a different object)

Coding Example 3 — Understanding IPCMNI Limit
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>

/* Read current system limits for semaphores */
int main(void) {
    FILE *f;
    char line[256];

    printf("=== System V IPC Limits ===\n\n");

    /* SEMMNI: max number of semaphore identifiers (sets) */
    f = fopen("/proc/sys/kernel/sem", "r");
    if (f) {
        int semmsl, semmns, semopm, semmni;
        if (fscanf(f, "%d %d %d %d", &semmsl, &semmns, &semopm, &semmni) == 4) {
            printf("SEMMSL  (max semaphores per set):        %d\n", semmsl);
            printf("SEMMNS  (max semaphores system-wide):    %d\n", semmns);
            printf("SEMOPM  (max ops per semop() call):      %d\n", semopm);
            printf("SEMMNI  (max semaphore sets):            %d\n", semmni);
        }
        fclose(f);
    }

    /* MSGMNI: max message queue identifiers */
    f = fopen("/proc/sys/kernel/msgmni", "r");
    if (f) {
        int v; fscanf(f, "%d", &v);
        printf("MSGMNI  (max message queues):            %d\n", v);
        fclose(f);
    }

    /* SHMMNI: max shared memory identifiers */
    f = fopen("/proc/sys/kernel/shmmni", "r");
    if (f) {
        int v; fscanf(f, "%d", &v);
        printf("SHMMNI  (max shared memory segments):    %d\n", v);
        fclose(f);
    }

    printf("\nSEQ_MULTIPLIER (IPCMNI): 32768\n");
    printf("seq wraps at: INT_MAX / IPCMNI = 2147483647 / 32768 = 65535\n");
    printf("Identifier reuse: possible after 65535 creations at same slot\n");

    return 0;
}

🎯 Interview Questions — Identifier Algorithm

Q1. What is the formula used by the Linux kernel to generate a System V IPC identifier?
A: identifier = index + (xxx_perm.__seq × SEQ_MULTIPLIER), where index is the position in the entries[] array, __seq is the sequence number saved at creation time, and SEQ_MULTIPLIER is 32,768 (IPCMNI).
Q2. What is SEQ_MULTIPLIER and what is its value on Linux?
A: SEQ_MULTIPLIER is IPCMNI, defined in the Linux kernel as 32,768. It defines the maximum number of System V IPC objects of each type that can exist simultaneously, and is the multiplier in the identifier calculation.
Q3. How do you find the entries[] array index from a known identifier?
A: index = identifier % SEQ_MULTIPLIER. This reverse formula is used by the kernel to quickly look up the associated data structure for a given identifier.
Q4. Under what circumstances could two different IPC objects have the same identifier?
A: If 65,535 objects are created at the same entries[] index (causing seq to wrap), and the slot is reused, the identifier would repeat. In practice this is extremely unlikely.
Q5. What is the difference between EINVAL and EIDRM errors for IPC system calls?
A: EINVAL is returned when the entries[] slot for the given index is empty (the object never existed or was freed and slot not yet reused). EIDRM is returned when the slot is occupied by a different object — the old object was deleted and the slot reused, but the sequence number doesn’t match the supplied identifier.
Q6. What is the ipc_ids structure and what are its key fields?
A: ipc_ids is the kernel’s global structure for tracking all instances of a particular IPC mechanism. Key fields: seq (global counter incremented on each object creation), entries (dynamically-sized array of pointers to associated data structures), size (current entries array size), max_id (highest in-use index), in_use (count of active objects).
Q7. Why does the seq counter in ipc_ids get incremented per object creation, not per deletion?
A: seq is incremented at creation time (step 2b of the algorithm), so each new object captures a unique seq value. This ensures identifiers are unique even if the same entries[] slot is reused. Incrementing at deletion would not prevent the race condition.

Leave a Reply

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