ftruncate() and mmap() Setting Size and Mapping POSIX Shared Memory Objects

 

ftruncate() and mmap()
Setting Size and Mapping POSIX Shared Memory Objects
Step 2+3
of 4
size
ftruncate
map
mmap

Two Steps to Get Usable Memory

After shm_open() gives you an fd, you cannot use the object yet — it has zero size. Two operations turn it into usable shared memory:

ftruncate(fd, size) — sets the object’s size (creator only). Think of it as “allocating” the shared memory.
mmap() — maps the object into the process’s virtual address space, returning a pointer you can use like any other memory.

ftruncate() — Setting the Size
#include <unistd.h>

int ftruncate(int fd, off_t length);
/* Returns: 0 on success, -1 on error */

A newly created shared memory object has size zero. ftruncate() sets it to exactly length bytes. The new bytes are zero-initialized by the kernel.

📦
After shm_open()
0 bytes
unusable
ftruncate(fd, 4096)
📦
After ftruncate()
4096 bytes
zero-initialized

Rules for ftruncate():

Only the creator should call it. If a reader calls ftruncate() on a read-only fd, it fails. The size can be increased or decreased after creation — but be careful if other processes already have it mapped.

/* Set size to 4096 bytes */
if (ftruncate(fd, 4096) == -1) {
    perror("ftruncate");
    exit(EXIT_FAILURE);
}

/* Shrink it later (only safe if no process is using the region) */
if (ftruncate(fd, 1024) == -1) {
    perror("ftruncate shrink");
    exit(EXIT_FAILURE);
}

mmap() — Mapping Into Address Space
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot,
           int flags, int fd, off_t offset);
/* Returns: mapped address on success, MAP_FAILED on error */

Parameter Typical value for shared memory Meaning
addr NULL Let kernel choose the address
length SHM_SIZE How many bytes to map
prot PROT_READ | PROT_WRITE Allow reads and writes via pointer
flags MAP_SHARED Writes visible to other processes
fd fd from shm_open() Which object to map
offset 0 Start from the beginning

Critical: Always use MAP_SHARED for IPC. If you use MAP_PRIVATE, writes create a private copy-on-write copy — other processes will never see your changes.

prot Flags — Memory Protection

The prot parameter controls what CPU operations are allowed on the mapped region:

PROT_READ
Allow reading bytes from the region
PROT_WRITE
Allow writing bytes into the region
PROT_EXEC
Allow executing code in the region
PROT_NONE
No access at all (guard pages)

prot must be compatible with how you opened the fd. If you opened with O_RDONLY, you cannot map with PROT_WRITE — the call will fail with EACCES.

Code Example — Writer: Create, Size, Map, Write
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define SHM_NAME "/ep_shm"
#define SHM_SIZE 4096

int main(void)
{
    int   fd;
    void *addr;

    /* Step 1: Create */
    fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }

    /* Step 2: Set size */
    if (ftruncate(fd, SHM_SIZE) == -1) {
        perror("ftruncate");
        exit(EXIT_FAILURE);
    }

    /* Step 3: Map into address space */
    addr = mmap(NULL,               /* kernel chooses address */
                SHM_SIZE,           /* how much to map */
                PROT_READ | PROT_WRITE,
                MAP_SHARED,         /* visible to other processes */
                fd,                 /* the shared memory fd */
                0);                 /* offset: start from beginning */

    if (addr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    /* fd no longer needed after mmap */
    close(fd);

    /* Write data directly into shared memory via pointer */
    snprintf((char *)addr, SHM_SIZE, "Hello from PID %d!", (int)getpid());
    printf("Writer: wrote '%s'\n", (char *)addr);

    /* Keep data alive — in real programs, use semaphore to signal reader */
    printf("Press Enter when reader is done...\n");
    getchar();

    /* Step 4: Cleanup */
    munmap(addr, SHM_SIZE);
    shm_unlink(SHM_NAME);

    return 0;
}
Code Example — Reader: Open, Map, Read
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define SHM_NAME "/ep_shm"
#define SHM_SIZE 4096

int main(void)
{
    int   fd;
    void *addr;

    /* Open existing object — read-only */
    fd = shm_open(SHM_NAME, O_RDONLY, 0);
    if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }

    /* Map — PROT_READ only, matching O_RDONLY */
    addr = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }

    close(fd);  /* fd not needed after mmap */

    printf("Reader: got '%s'\n", (char *)addr);

    munmap(addr, SHM_SIZE);
    return 0;
}

Run in two terminals:

# Terminal 1
gcc writer.c -o writer -lrt
./writer
# Writer: wrote 'Hello from PID 12345!'
# Press Enter when reader is done...

# Terminal 2
gcc reader.c -o reader -lrt
./reader
# Reader: got 'Hello from PID 12345!'

# Back in Terminal 1: press Enter to cleanup

Resizing a Shared Memory Object at Runtime

Unlike System V shared memory (where size is fixed at creation), POSIX shared memory supports dynamic resizing via ftruncate(). To grow the object:

/* Resize shared memory from 4096 to 8192 bytes */
int fd = shm_open("/ep_shm", O_RDWR, 0);
if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }

if (ftruncate(fd, 8192) == -1) { perror("ftruncate"); exit(EXIT_FAILURE); }

/* Old mapping is stale — unmap and remap */
munmap(old_addr, 4096);
void *new_addr = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
                      MAP_SHARED, fd, 0);
close(fd);

Warning: After resizing, other processes that already have the object mapped must also munmap() and re-mmap() to see the new size. Their existing mapping is not automatically extended. On Linux, mremap() can sometimes extend a mapping in-place.

munmap() — Unmapping the Region
#include <sys/mman.h>

int munmap(void *addr, size_t length);
/* Returns: 0 on success, -1 on error */

munmap() removes the mapping from the process’s address space. It does NOT delete the shared memory object. Other processes retain their mappings. The object persists until shm_unlink() is called.

The kernel automatically unmaps all regions when a process exits, but calling munmap() explicitly is good practice — especially for long-running daemons that create many mappings.

Interview Questions — ftruncate and mmap

Q1. Why must ftruncate() be called after shm_open() before mmap()?

A new shared memory object starts with zero size. mmap() on a zero-size object would fail with EINVAL. ftruncate() sets the size so the kernel can actually allocate backing pages.

Q2. What is the difference between MAP_SHARED and MAP_PRIVATE?

MAP_SHARED means writes to the mapped region are visible to all other processes that have the same object mapped, and changes are written back to the underlying object. MAP_PRIVATE creates a copy-on-write copy — writes are private to the process and not visible to others. For IPC, always use MAP_SHARED.

Q3. After calling close(fd) following mmap(), can you still access the shared memory?

Yes. mmap() adds a reference to the underlying object. Closing the fd only affects the file descriptor — the mapping itself stays valid until munmap() is called or the process exits.

Q4. What does mmap() return on error?

It returns MAP_FAILED, which is defined as (void *) -1. Never check if (addr == NULL) — a NULL return is valid on some systems. Always check if (addr == MAP_FAILED).

Q5. Can the same process map the same shared memory object multiple times?

Yes. Each mmap() call returns a separate virtual address range, but they all point to the same physical pages. Changes through one pointer are immediately visible through the other pointers (since they’re the same physical memory).

Q6. What happens to data in shared memory if a process calls munmap() but not shm_unlink()?

The shared memory object persists. Other processes can still open and map it. The data is intact. Only shm_unlink() destroys the object (after all mappings are closed).

Leave a Reply

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