POSIX IPC — Open Calls, Permissions & Closing

 

POSIX IPC — Open Calls, Permissions & Closing
Chapter 51 · File 1 of 3 · Linux System Programming (TLPI)

What You Will Learn

This file covers the fundamentals of POSIX IPC — what the three mechanisms are, how to open/create IPC objects, how permissions are set and checked, and how to properly close IPC objects when done.

1. The Three POSIX IPC Mechanisms

POSIX IPC consists of three separate mechanisms. Each solves a different communication problem between processes:

Mechanism Open Function Purpose Close / Remove
Message Queue mq_open() Exchange messages between processes mq_close() / mq_unlink()
Semaphore sem_open() Synchronize access to shared resources sem_close() / sem_unlink()
Shared Memory shm_open() Share a memory region between processes munmap() / shm_unlink()

All three follow the same file-like model: create with a name, open it, use it, close it, and unlink (delete) it when done.

2. IPC Open Calls — How to Create and Open POSIX IPC Objects

Every POSIX IPC mechanism has a corresponding open call. The general signature pattern looks like this:

/* General pattern for POSIX IPC open calls */

/* Message Queue */
mqd_t mq_open(const char *name, int oflag, ...);

/* Named Semaphore */
sem_t *sem_open(const char *name, int oflag, ...);

/* Shared Memory */
int shm_open(const char *name, int oflag, mode_t mode);

2.1 The name Parameter

IPC objects are identified by a name — a string that looks like a file path. On Linux:

  • The name must start with a forward slash (/)
  • Only one slash is used (no subdirectories)
  • Example: /my_queue, /app_sem, /shared_buf
/* Correct IPC object names on Linux */
const char *mq_name  = "/my_message_queue";
const char *sem_name = "/app_semaphore";
const char *shm_name = "/shared_buffer";

/* WRONG — subdirectories not allowed */
const char *bad_name = "/app/my_queue";   /* ERROR on Linux */

2.2 The oflag Parameter

The oflag controls how the object is opened. It works just like the flags in open() for files:

Flag Meaning
O_RDONLY Open for reading only
O_WRONLY Open for writing only
O_RDWR Open for both reading and writing
O_CREAT Create the object if it does not exist
O_EXCL Used with O_CREAT — fail if object already exists
O_NONBLOCK Non-blocking open (message queues)
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    mqd_t mqd;

    /*
     * O_CREAT | O_RDWR:
     *   Create the queue if it doesn't exist.
     *   Open it for both reading and writing.
     * 0644: rw-r--r-- permissions on the queue object.
     */
    mqd = mq_open("/demo_queue", O_CREAT | O_RDWR, 0644, NULL);
    if (mqd == (mqd_t)-1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    printf("Message queue opened successfully. Descriptor: %d\n", (int)mqd);

    mq_close(mqd);
    mq_unlink("/demo_queue");
    return 0;
}

Compile and run:

gcc -o demo_mq demo_mq.c -lrt
./demo_mq

2.3 O_CREAT + O_EXCL — Exclusive Creation

If you use O_CREAT | O_EXCL together, the call fails with EEXIST if the object already exists. This is useful when only one process should create the object.

#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

int main(void)
{
    sem_t *sem;

    /* Try to create exclusively — will fail if already exists */
    sem = sem_open("/exclusive_sem", O_CREAT | O_EXCL, 0600, 1);

    if (sem == SEM_FAILED) {
        if (errno == EEXIST) {
            printf("Semaphore already exists — someone else created it.\n");
            /* Open without O_CREAT to use the existing one */
            sem = sem_open("/exclusive_sem", 0);
            if (sem == SEM_FAILED) {
                perror("sem_open");
                exit(EXIT_FAILURE);
            }
        } else {
            perror("sem_open");
            exit(EXIT_FAILURE);
        }
    } else {
        printf("Semaphore created successfully by this process.\n");
    }

    sem_close(sem);
    return 0;
}

3. IPC Object Permissions — mode and umask

When you create a POSIX IPC object (using O_CREAT), you must specify a mode — the permission bits for the new object. These work exactly like file permissions.

3.1 Permission Bits

Octal Symbolic Meaning
0400 S_IRUSR Owner read
0200 S_IWUSR Owner write
0040 S_IRGRP Group read
0020 S_IWGRP Group write
0004 S_IROTH Others read
0002 S_IWOTH Others write

Important: Execute permission (0100, 0010, 0001) has no meaning for POSIX IPC objects. Setting it is allowed but ignored.

3.2 How umask Affects Permissions

The mode you specify is not used directly. The kernel applies the process’s umask to it:

/* Effective permission formula */
effective_mode = mode & ~umask
Example:
mode = 0666 → 110 110 110
umask = 0022 → 000 010 010 (bits to remove)
result = 0644 → 110 100 100 (rw-r–r–)
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int fd;

    /*
     * Request permissions: 0660 (owner rw, group rw, others none)
     * If umask is 0022, the 0020 (group write) bit will be removed.
     * Actual result: 0640 (owner rw, group r, others none)
     */
    fd = shm_open("/perm_demo", O_CREAT | O_RDWR, 0660);
    if (fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }

    printf("Shared memory object created.\n");
    printf("Requested mode: 0660, actual depends on current umask.\n");

    /* Tip: check with ls -l /dev/shm/perm_demo after running */

    close(fd);
    shm_unlink("/perm_demo");
    return 0;
}

3.3 Ownership of a New IPC Object

When a new POSIX IPC object is created, its ownership is set to the effective user ID and effective group ID of the creating process. On Linux specifically, it uses the process’s filesystem IDs (which normally equal the effective IDs).

3.4 ACL Support (Linux 2.6.19+)

Since Linux kernel 2.6.19, Access Control Lists (ACLs) can be used to set fine-grained permissions on:

  • POSIX shared memory objects
  • Named semaphores

ACLs are not currently supported for POSIX message queues.

4. Closing POSIX IPC Objects

When a process finishes using a POSIX IPC object, it must close it. This tells the kernel to release any per-process resources associated with the object.

IPC Type Close Call Notes
Message Queue mq_close(mqd) Closes the message queue descriptor
Semaphore sem_close(sem) Releases the semaphore from this process
Shared Memory munmap(addr, len) Unmaps the region — no separate close call

Key point: Closing an IPC object does not delete it. The object continues to exist until it is explicitly unlinked. Other processes can still open it after you close it.

IPC objects are automatically closed when a process:

  • Terminates (normal exit or signal)
  • Calls exec() to execute a new program
#include <mqueue.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    /* --- Message Queue --- */
    mqd_t mqd = mq_open("/close_demo_mq", O_CREAT | O_RDWR, 0644, NULL);
    if (mqd == (mqd_t)-1) { perror("mq_open"); exit(1); }
    printf("MQ opened.\n");

    if (mq_close(mqd) == -1) { perror("mq_close"); }
    else printf("MQ closed. Object still exists in kernel.\n");

    /* mq_unlink removes the name. The object itself is gone when */
    /* all processes that had it open have closed it.             */
    mq_unlink("/close_demo_mq");
    printf("MQ unlinked.\n\n");

    /* --- Named Semaphore --- */
    sem_t *sem = sem_open("/close_demo_sem", O_CREAT, 0600, 1);
    if (sem == SEM_FAILED) { perror("sem_open"); exit(1); }
    printf("Semaphore opened.\n");

    if (sem_close(sem) == -1) { perror("sem_close"); }
    else printf("Semaphore closed.\n");

    sem_unlink("/close_demo_sem");
    printf("Semaphore unlinked.\n\n");

    /* --- Shared Memory --- */
    int fd = shm_open("/close_demo_shm", O_CREAT | O_RDWR, 0644);
    if (fd == -1) { perror("shm_open"); exit(1); }
    ftruncate(fd, 4096);

    void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
    close(fd); /* The fd can be closed after mmap — mapping stays */
    printf("Shared memory mapped.\n");

    strcpy((char *)addr, "Hello from shared memory!");
    printf("Wrote: %s\n", (char *)addr);

    /* munmap is the "close" for shared memory */
    if (munmap(addr, 4096) == -1) { perror("munmap"); }
    else printf("Shared memory unmapped.\n");

    shm_unlink("/close_demo_shm");
    printf("Shared memory unlinked.\n");

    return 0;
}

Compile:

gcc -o close_demo close_demo.c -lrt -lpthread
./close_demo

5. Compiling POSIX IPC Programs on Linux

On Linux, POSIX IPC functions live in the realtime library (librt). You must link against it with -lrt. For semaphores, you also need -lpthread.

# Message queues
gcc -o mq_prog mq_prog.c -lrt

# Semaphores
gcc -o sem_prog sem_prog.c -lrt -lpthread

# Shared memory
gcc -o shm_prog shm_prog.c -lrt

# All three (safe default)
gcc -o ipc_prog ipc_prog.c -lrt -lpthread

Without -lrt, you will get a linker error like:

undefined reference to `mq_open'
undefined reference to `shm_open'

🎯 Interview Questions — POSIX IPC Overview
Q1: What are the three POSIX IPC mechanisms?

POSIX Message Queues, POSIX Named Semaphores, and POSIX Shared Memory. All three use a file-like model with open/close/unlink calls.

Q2: How must a POSIX IPC object name be formed on Linux?

It must start with a single forward slash and contain no further slashes. For example, /my_queue is valid but /app/queue is not.

Q3: What is the purpose of O_EXCL when used with O_CREAT?

It makes the open call fail with EEXIST if the object already exists. This ensures only one process creates the object and avoids accidental reuse of a leftover object.

Q4: How does umask affect POSIX IPC object permissions?

The kernel applies the process’s umask to the requested mode: effective = mode & ~umask. So if you request 0666 and the umask is 0022, the actual permissions will be 0644.

Q5: Does execute permission make sense for POSIX IPC objects?

No. Execute permission has no meaning for POSIX IPC objects. You can set the bits, but the kernel ignores them.

Q6: How do you “close” a POSIX shared memory object?

There is no shm_close(). Shared memory is closed by unmapping the region with munmap(addr, length). The underlying file descriptor returned by shm_open() can be closed with the regular close() after mmap() is called.

Q7: What linker flag is required to compile POSIX IPC programs on Linux?

-lrt links the realtime library. For semaphores, you also need -lpthread. Without -lrt the linker will report undefined references to functions like mq_open and shm_open.

Q8: What happens to open POSIX IPC objects when a process calls exec()?

They are automatically closed, just as open file descriptors are closed on exec. The IPC objects themselves are not deleted — only the process’s references to them are released.

Q9: What Linux kernel version introduced ACL support for POSIX IPC?

Linux 2.6.19. ACLs are supported for POSIX shared memory and named semaphores, but not for POSIX message queues.

Leave a Reply

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