Synchronizing Linux Shared Memory IPC: Semaphores, Mutexes, and Condition Variables (with Code)
The article explains how to ensure data correctness in Linux inter‑process shared memory communication by using synchronization mechanisms—semaphores, mutexes, and condition variables—providing step‑by‑step code examples for each method and discussing their proper initialization, usage, and potential pitfalls.
When multiple Linux processes share a memory segment, explicit synchronization is required to keep the data consistent. The article covers three common mechanisms—System V semaphores, POSIX mutexes, and POSIX condition variables—showing how each can be applied to protect reads and writes.
1. Semaphore synchronization
A System V semaphore is created with semget, initialized to 1 with semctl, and used to guard the shared memory region. The program first creates the segment with shmget, attaches it via shmat, then performs the following steps:
Wait for the semaphore (a semop with sem_op = 0).
Write data to the memory.
Release the semaphore (a semop with sem_op = 1).
Repeat the wait‑write‑release cycle for reading.
Detach and remove the shared memory and semaphore.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#define SHM_SIZE 1024
#define SEM_KEY 0x123456
union semun { int val; struct semid_ds *buf; unsigned short *array; };
int main() {
int shmid, semid;
char *shmaddr;
struct sembuf semops[2];
union semun semarg;
/* create shared memory */
shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) { perror("shmget"); exit(1); }
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) { perror("shmat"); exit(1); }
/* create semaphore */
semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (semid == -1) { perror("semget"); exit(1); }
semarg.val = 1;
if (semctl(semid, 0, SETVAL, semarg) == -1) { perror("semctl"); exit(1); }
/* wait (P) */
semops[0].sem_num = 0; semops[0].sem_op = 0; semops[0].sem_flg = 0;
if (semop(semid, semops, 1) == -1) { perror("semop"); exit(1); }
/* critical section */
strncpy(shmaddr, "Hello, world!", SHM_SIZE);
/* signal (V) */
semops[0].sem_op = 1;
if (semop(semid, semops, 1) == -1) { perror("semop"); exit(1); }
/* read back */
semops[0].sem_op = 0;
if (semop(semid, semops, 1) == -1) { perror("semop"); exit(1); }
printf("Received message: %s
", shmaddr);
/* cleanup */
if (shmdt(shmaddr) == -1) { perror("shmdt"); exit(1); }
if (shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl"); exit(1); }
if (semctl(semid, 0, IPC_RMID, semarg) == -1) { perror("semctl"); exit(1); }
return 0;
}The code demonstrates creating a single semaphore, acquiring it before writing, releasing it after, and performing the same steps for reading. The article warns that improper ordering can cause deadlocks.
2. Mutex synchronization
POSIX mutexes from pthread can also be placed in shared memory. The mutex must be initialized with the PTHREAD_PROCESS_SHARED attribute so that multiple processes can lock the same object.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHM_SIZE 1024
typedef struct {
pthread_mutex_t mutex;
char data[SHM_SIZE];
} shm_data_t;
int main() {
int fd;
shm_data_t *shm_data;
pthread_mutexattr_t mutex_attr;
pthread_mutex_t *mutex;
fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);
if (fd == -1) { perror("shm_open"); exit(1); }
if (ftruncate(fd, sizeof(shm_data_t)) == -1) { perror("ftruncate"); exit(1); }
shm_data = mmap(NULL, sizeof(shm_data_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm_data == MAP_FAILED) { perror("mmap"); exit(1); }
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
mutex = &(shm_data->mutex);
pthread_mutex_init(mutex, &mutex_attr);
/* write */
pthread_mutex_lock(mutex);
sprintf(shm_data->data, "Hello, world!");
pthread_mutex_unlock(mutex);
/* read */
pthread_mutex_lock(mutex);
printf("Received message: %s
", shm_data->data);
pthread_mutex_unlock(mutex);
if (munmap(shm_data, sizeof(shm_data_t)) == -1) { perror("munmap"); exit(1); }
if (shm_unlink("/my_shm") == -1) { perror("shm_unlink"); exit(1); }
return 0;
}Key points highlighted are the need to set the mutex attribute to PTHREAD_PROCESS_SHARED, to store the mutex inside the shared segment, and to lock/unlock around every access. Forgetting either step can lead to data races or crashes.
3. Condition variable synchronization
Condition variables are typically paired with a mutex to allow a process to wait for a specific event (e.g., data being written). Both the mutex and the condition variable must be created with the PTHREAD_PROCESS_SHARED attribute.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#define SHM_SIZE 4096
#define SHM_NAME "/myshm"
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
char buffer[SHM_SIZE];
} shm_t;
int main(int argc, char *argv[]) {
int fd, pid;
shm_t *shm;
pthread_mutexattr_t mutex_attr;
pthread_condattr_t cond_attr;
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0) { perror("shm_open"); exit(1); }
if (ftruncate(fd, sizeof(shm_t)) < 0) { perror("ftruncate"); exit(1); }
shm = mmap(NULL, sizeof(shm_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm == MAP_FAILED) { perror("mmap"); exit(1); }
pthread_mutexattr_init(&mutex_attr);
pthread_condattr_init(&cond_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&shm->mutex, &mutex_attr);
pthread_cond_init(&shm->cond, &cond_attr);
pid = fork();
if (pid < 0) { perror("fork"); exit(1); }
if (pid == 0) { /* child writes */
sleep(1);
pthread_mutex_lock(&shm->mutex);
sprintf(shm->buffer, "Hello, world!");
pthread_cond_signal(&shm->cond);
pthread_mutex_unlock(&shm->mutex);
exit(0);
} else { /* parent reads */
pthread_mutex_lock(&shm->mutex);
pthread_cond_wait(&shm->cond, &shm->mutex);
printf("Received message: %s
", shm->buffer);
pthread_mutex_unlock(&shm->mutex);
}
if (shm_unlink(SHM_NAME) < 0) { perror("shm_unlink"); exit(1); }
return 0;
}The example creates a shared memory object, initializes a mutex and a condition variable, forks a child process, and uses pthread_cond_wait in the parent to block until the child signals that data is ready. The article stresses that the waiting thread must hold the mutex, that spurious wake‑ups require a loop to re‑check the condition, and that signals may be lost if the order of lock‑wait‑signal is incorrect.
Conclusion
By choosing the appropriate synchronization primitive—semaphore, mutex, or condition variable—and by following the correct initialization and usage patterns, developers can safely coordinate multiple Linux processes that share memory, avoiding race conditions, deadlocks, and data corruption.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
