Writing Data
ftruncate + memcpy
Writer Process
Once a shared memory object exists (created by another process or the same one), a writer process opens it, optionally resizes it to fit the data, maps it, and copies data into the mapped region using memcpy() or direct pointer assignment. This is the fastest way to hand data to another process — no kernel involvement per write.
The key insight: after mmap() you have a regular C pointer. Writing to that pointer writes directly into the shared pages. Any other process that has mapped the same object will immediately see the change (subject to CPU cache coherency, which the kernel and hardware manage for you on the same machine).
| shm_open(name, O_RDWR, 0) Open existing object |
→ | ftruncate(fd, len) Resize to match data |
→ | mmap(…, fd, 0) Map into address space |
| ↓ | ||||
| memcpy(addr, data, len) Copy data into shared region |
→ | close(fd) fd no longer needed; mapping stays |
||
This is the program from TLPI Listing 54-2. It opens an existing shared memory object and copies a string into it, resizing the object first:
/* pshm_write.c
* Opens an existing POSIX shared memory object and writes a string into it.
* Usage: ./pshm_write shm-name string
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
size_t len;
char *addr;
if (argc != 3) {
fprintf(stderr, "Usage: %s shm-name string\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Open the EXISTING shared memory object for read+write */
fd = shm_open(argv[1], O_RDWR, 0);
if (fd == -1) {
perror("shm_open");
exit(EXIT_FAILURE);
}
/* Resize the object to exactly hold the string */
len = strlen(argv[2]);
if (ftruncate(fd, len) == -1) {
perror("ftruncate");
exit(EXIT_FAILURE);
}
printf("Resized to %ld bytes\n", (long)len);
/* Map the object into the process address space */
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
/* Close the fd — we no longer need it; the mapping stays active */
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
/* Copy the string into the shared region */
printf("Copying %ld bytes\n", (long)len);
memcpy(addr, argv[2], len);
/* The mapping is cleaned up on exit, but let's be explicit */
munmap(addr, len);
exit(EXIT_SUCCESS);
}
Compile and run:
# First create the object (from Part 1 program):
./pshm_create -c /demo_shm 0
# Write a string into it:
./pshm_write /demo_shm "hello world"
# Check the size changed:
ls -l /dev/shm/demo_shm
A common question: once you have called mmap(), the file descriptor is no longer needed to access the shared memory. The mapping is maintained by the kernel independently of the fd.
Closing the fd early is good practice:
- Frees up a file descriptor slot
- Prevents accidental use of the fd later in the code
- The mapping remains valid until
munmap()or process exit
/* After mmap(), fd can be closed safely */
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd); /* fd no longer needed — mapping stays */
/* Now use addr directly */
strcpy(addr, "safe to use addr even after close(fd)");
ftruncate() can both grow and shrink a shared memory object:
- Growing: New bytes are zero-filled
- Shrinking: Data beyond the new size is discarded. Any existing mappings that extend beyond the new size will cause
SIGBUSif accessed
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main(void)
{
int fd = shm_open("/resize_demo", O_CREAT | O_RDWR, 0600);
if (fd == -1) { perror("shm_open"); exit(1); }
/* Start at 1024 bytes */
ftruncate(fd, 1024);
char *addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
/* Write to byte 1000 — fine */
addr[1000] = 'X';
printf("addr[1000] = %c\n", addr[1000]);
/* Grow to 2048 — new bytes are zero-filled */
ftruncate(fd, 2048);
printf("addr[1500] (new, zeroed) = %d\n", (int)addr[1500]);
munmap(addr, 1024);
close(fd);
shm_unlink("/resize_demo");
return 0;
}
You are not limited to strings. You can write any C struct into shared memory:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
/* Shared data structure */
typedef struct {
int sensor_id;
float temperature;
float humidity;
char label[32];
} SensorData;
int main(void)
{
int fd = shm_open("/sensor_shm", O_CREAT | O_RDWR, 0600);
if (fd == -1) { perror("shm_open"); exit(1); }
ftruncate(fd, sizeof(SensorData));
SensorData *data = mmap(NULL, sizeof(SensorData),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
/* Write structured data directly */
data->sensor_id = 42;
data->temperature = 36.6f;
data->humidity = 65.0f;
strncpy(data->label, "Room-A", sizeof(data->label));
printf("Written sensor_id=%d, temp=%.1f, hum=%.1f, label=%s\n",
data->sensor_id, data->temperature,
data->humidity, data->label);
munmap(data, sizeof(SensorData));
/* Don't unlink yet — reader will open it */
return 0;
}
If two processes write to the same shared memory region simultaneously without any coordination, the result is a data race — the data will be corrupted. Shared memory provides no built-in locking.
You must use a synchronization primitive such as:
- POSIX semaphores (
sem_wait/sem_post) - A mutex stored inside the shared memory itself (using
pthread_mutexattr_setpshared) - File locking (
fcntl/flock) on the fd
/* Pattern: use a semaphore to protect shared memory writes */
#include <semaphore.h>
#include <fcntl.h>
#include <sys/mman.h>
/* Writer side */
sem_t *sem = sem_open("/my_sem", O_CREAT, 0600, 1); /* initial value=1 */
char *shm = /* ... mmap ... */;
sem_wait(sem); /* lock */
memcpy(shm, data, len);/* write */
sem_post(sem); /* unlock */
Synchronization is covered in depth in Part 4 of this series.
This example shows a producer that writes timestamped messages into shared memory in a loop:
/* producer.c — writes messages to shared memory */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <time.h>
#define SHM_NAME "/prod_cons"
#define BUF_SIZE 256
typedef struct {
int seq;
char message[BUF_SIZE];
} SharedMsg;
int main(void)
{
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
if (fd == -1) { perror("shm_open"); exit(1); }
ftruncate(fd, sizeof(SharedMsg));
SharedMsg *msg = mmap(NULL, sizeof(SharedMsg),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (msg == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
for (int i = 1; i <= 5; i++) {
msg->seq = i;
snprintf(msg->message, BUF_SIZE,
"Message #%d from producer (pid=%d)", i, getpid());
printf("Wrote: %s\n", msg->message);
sleep(1);
}
munmap(msg, sizeof(SharedMsg));
printf("Producer done. Run consumer now.\n");
return 0;
}
# Compile:
gcc -o producer producer.c -lrt
./producer &
# Then run the consumer (from Part 3)
Q1. In pshm_write, the program calls ftruncate() before mmap(). Why?
The shared memory object must have a non-zero size before it can be meaningfully mapped. The program resizes it to exactly the length of the string it wants to copy, then maps that exact size. This avoids wasting memory and ensures the reader (which uses fstat() to query the size) gets the correct byte count.
Q2. After calling mmap(), can we close() the file descriptor? Will the mapping survive?
Yes. Once mmap() has been called, the kernel maintains the mapping independently of the file descriptor. Closing the fd does not affect the mapping. The mapping remains valid until munmap() is called explicitly, or the process exits.
Q3. What is MAP_SHARED in mmap() and why is it required for IPC?
MAP_SHARED means writes to the mapped region are reflected back to the underlying object (the shared memory file) and are visible to other processes that have mapped the same object. The alternative MAP_PRIVATE uses copy-on-write semantics — changes are private to the process and never written back, making IPC impossible.
Q4. What happens if you call ftruncate() to shrink a shared memory object while another process has it mapped at the old (larger) size?
The other process’s mapping still covers the old range in its virtual address space. If it tries to access memory beyond the new (smaller) size, it receives a SIGBUS signal, which by default terminates the process. This is why coordinating size changes with all processes sharing the object is important.
Q5. Can you use write() instead of memcpy() to put data into a shared memory mapped region?
No. Once the object is mapped, the pointer addr is a regular memory pointer. write() is a system call that operates on file descriptors, not on memory pointers. You use memcpy(), strcpy(), struct assignment, or any normal C memory operation. (You could still call write(fd, ...) using the fd directly, but that bypasses the mapping entirely.)
Q6. What protection flags would you use for a read-only consumer mapping?
PROT_READ only. Attempting to write to a PROT_READ mapping causes a SIGSEGV. A read-only mapping is also good practice for consumers to prevent accidental modification of shared data.
Next: Reading data from a shared memory object using fstat and mmap
