I have two processes (result of a fork) that need to share memory between each other. I synchronized read/write into this memory using a semaphore (which works super fine), but when one writes to the shared memory, the other one cannot get the value written. I sure am missing something but I cannot figure out what. I tried to share memory using shmget, shmat etc too without success, so I stayed with shm_open+mmap.
Minimal reproducible example:
main.c
#include "../headers/pid_handling.h"
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int pid = fork();
if(pid == 0){
shm_lock();
puts("====");
shm_pid_list * p = shm_get_pid_list_copy();
printf("Nb of pids : %ld\n", p->number_of_pid);
shm_add_pid(getpid());
shm_add_pid(getpid());
p = shm_get_pid_list_copy();
for (size_t i = 0; i < p->number_of_pid; i++)
{
printf("From %d | %d | %lu\n", getpid(), p->pids[i], p->number_of_pid);
}
shm_unlock();
} else {
shm_lock();
puts("====");
shm_pid_list * p = shm_get_pid_list_copy();
printf("Nb of pids : %ld\n", p->number_of_pid);
shm_add_pid(getpid());
p = shm_get_pid_list_copy();
printf("%d | %ld\nList of pids: \n", getpid(), p->number_of_pid);
for (size_t i = 0; i < p->number_of_pid; i++)
{
printf("From %d | %d | %lu\n", getpid(), p->pids[i], p->number_of_pid);
}
shm_unlock();
exit(1);
}
shm_free_shared_memory();
return 1;
}
pid_handling.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <semaphore.h>
#include <sys/mman.h>
#include "../headers/utils.h"
#include "../headers/pid_handling.h"
#include "../headers/safe_memory.h"
#define SHM_SIZE 4096 * 4 // TODO : properly handle size, might need mremap. Current size is way more than enough to handle PIDs
#define SEM_ID "/SEM_PIDLIST_PRELOAD"
#define SHM_ID "/SHM_PIDLIST_PRELOAD"
#define SEM_INITIAL_VALUE_UNLOCKED 1
#define SHM_KEY_VAL 0x2337B4B3
// see https://stackoverflow.com/questions/23668080/memory-reallocation-using-mremap
/*
* The purpose of this file is to propose a way for logged
* process to always be able to know its PID and the PID
* of process it created.
*
* The representation of the memory is defined in the struct shm_pid_list
* and is composed of the number of pids and a list of those pids.
*/
key_t shm_key = SHM_KEY_VAL;
int shm_id = 0;
int fd_lock = 0;
sem_t* semaphore = SEM_FAILED;
char* shm_ptr = NULL;
/**
* Creates a shared memory using shm_open and returns the associated file descriptor.
*
* @return The file descriptor associated with the created shm. Abort the program if it fails.
*/
int __create_shared_memory(){
int shm_id = shm_open(SHM_ID, O_CREAT | O_RDWR, 0600);
if(shm_id == -1) handle_error("shm_open");
return shm_id;
}
/**
* Initialize the shared memory if necessary.
*
* @return The file descriptor associated with the created shm. Abort the program if it fails.
*/
void __shm_init_shared_memory(size_t size) {
// Opens the shared memory. Creates it if it does not exist.
if( !shm_id ) {
shm_id = __create_shared_memory();
}
ftruncate(shm_id, size);
shm_ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0);
if (shm_ptr == MAP_FAILED) {
handle_error("mmap shared mem");
}
shm_unlink(SHM_ID);
}
void* __shm_get_shared_memory(){
// If shared memory is not initialized yet, initialize it.
if(!shm_id || !shm_ptr) {
__shm_init_shared_memory(SHM_SIZE);
}
return shm_ptr;
}
sem_t* __shm_get_lock(){
return semaphore;
}
void __shm_create_lock(){
semaphore = sem_open(SEM_ID, O_CREAT | O_RDWR, 0600, SEM_INITIAL_VALUE_UNLOCKED);
// File will be deleted upon program termination
// but is still opened till then (see unlink man)
sem_unlink(SEM_ID);
if(semaphore == SEM_FAILED) handle_error("sem_open");
}
/* Note that this also closes the lock */
void shm_free_shared_memory(){
if(!shm_id) return;
if(close(shm_id) == -1) {
puts("could not close fd shm");
return;
}
if(sem_close(semaphore) == -1){
puts("Err closing fd_lock");
return;
}
}
void shm_lock(){
if(semaphore == SEM_FAILED){
__shm_create_lock();
}
sem_wait(semaphore);
printf("%d locked\n", getpid());
}
void shm_unlock(){
if(semaphore == SEM_FAILED){
__shm_create_lock();
}
sem_post(semaphore);
printf("%d unlocked\n", getpid());
}
/**
* Returns a copied list of pids. The copy is made in order to prevent race condition between
* a process that would write a new pid to the shm and one that would want to read the list
* at a given point.
*
* @return A copied pid_list the caller has to free.
*/
shm_pid_list* shm_get_pid_list_copy(){
shm_pid_list* p = s_calloc(1, sizeof(shm_pid_list));
shm_pid_list* shm = __shm_get_shared_memory();
printf("Getting pid list with %ld pids\n", shm->number_of_pid);
memcpy(p, shm, sizeof(shm->number_of_pid) + sizeof(pid_t) * shm->number_of_pid);
return p;
}
pid_t shm_get_pid(unsigned long n){
shm_pid_list * shm = shm_get_pid_list_copy();
if(n > shm->number_of_pid) return -1;
return shm->pids[n];
}
void shm_add_pid(pid_t p){
unsigned long n = 0;
shm_pid_list* shm = __shm_get_shared_memory();
n = shm->number_of_pid;
shm->pids[n] = p;
shm->number_of_pid++;
printf("Added pid %d cont is now %ld\n", getpid(), shm->number_of_pid);
}
Files safe_memory.h contains definitions for s_malloc, s_calloc, s_realloc (which is basic malloc/calloc/realloc with error handling). utils.h holds the following:
#define handle_error(msg) \
do { perror(PRELOAD_ERROR msg); exit(EXIT_FAILURE); } while (0)
The whole thing is compiled using : gcc main.c safe_memory.c pid_handling.c -lrt -lpthread (or you could paste main.c in pid_handling.c to avoid having to write .h files).
Result
23539 locked
====
Getting pid list with 0 pids
Nb of pids : 0
Added pid 23539 cont is now 1
Getting pid list with 1 pids
23539 | 1
List of pids:
From 23539 | 23539 | 1
23539 unlocked
23540 locked
====
Getting pid list with 0 pids
Nb of pids : 0
Added pid 23540 cont is now 1
Added pid 23540 cont is now 2
Getting pid list with 2 pids
From 23540 | 23540 | 2
From 23540 | 23540 | 2
23540 unlocked
Expected result
23539 locked
====
Getting pid list with 0 pids
Nb of pids : 0
Added pid 23539 cont is now 1
Getting pid list with 1 pids
23539 | 1
List of pids:
From 23539 | 23539 | 1
23539 unlocked
23540 locked
====
Getting pid list with 1 pids
Nb of pids : 1
Added pid 23540 cont is now 2
Added pid 23540 cont is now 3
Getting pid list with 3 pids
From 23540 | 23539 | 1
From 23540 | 23540 | 2
From 23540 | 23540 | 3
23540 unlocked
I would be glad if anyone would have any idea about why memory write isn't visible from the other process.