Shared memory problem post fork is not getting "shared" (but opens without error)

Viewed 94

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.

0 Answers
Related