Search code examples
cipcshared-memorymmap

memory efficient way of implementing a circular buffer using mmap


I am implementing IPC using shared memory using mmap. The structure i am using for sharing is

struct shared{
    sem_t P;
    sem_t C;
    sem_t M;
    int prod_status;
    char** queue;
    int buffer_size;
    int queue_start;
    int queue_after_last;       //pointing to buffer index after the last element in the buffer
    int queue_count;
};

The size of the buffer is passed as command line argument.

int main(int agrc, char* argv[]){
    int N_buff=atoi(argv[1]);
    struct shared* shared_data=(struct shared*)mmap(NULL,sizeof(struct shared),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    //printf("hello\n");
    shared_data->buffer_size=N_buff;
    shared_data->queue=(char**)mmap(NULL,sizeof(N_buff),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    for(int i=0;i<N_buff;i++){
        shared_data->queue[i]=(char*)mmap(NULL,70*sizeof(char),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    }
    shared_data->queue_start=0;
    shared_data->queue_after_last=0;
    shared_data->queue_count=0;
    shared_data->prod_status=1;
    sem_init(&shared_data->P,1,N_buff);
    sem_init(&shared_data->C,1,0);
    sem_init(&shared_data->M,1,1);};

The queue is accessed by other process. I am using the queue as pipe. The question is, everytime I allocated memory using

shared_data->queue[i]=(char*)mmap(NULL,70*sizeof(char),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);

It will allocate whole page of memory. Is there any other memory efficient way of implementing this using mmap? I am using fork after this code to spawn a child process, so I guess I can use pointers for IPC between parent and child using this method.


Solution

  • Your code had some bugs.

    The second mmap call used sizeof(N_buff) [which is always 4] instead of: sizeof(*shared_data->queue) * N_buff

    It is possible to do a single mmap for all the data [see below].


    Here's the annotated and corrected code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <semaphore.h>
    #include <sys/sem.h>
    #include <sys/mman.h>
    
    struct shared {
        sem_t P;
        sem_t C;
        sem_t M;
        int prod_status;
        char **queue;
        int buffer_size;
        int queue_start;
        // pointing to buffer index after the last element in the buffer
        int queue_after_last;
        int queue_count;
    };
    
    int
    main(int agrc, char *argv[])
    {
        int N_buff = atoi(argv[1]);
        struct shared *shared_data;
    
        shared_data = mmap(NULL, sizeof(struct shared), PROT_READ | PROT_WRITE,
            MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
        // printf("hello\n");
        shared_data->buffer_size = N_buff;
    // NOTE/BUG: sizeof(N_buff) is _always_ 4
    #if 0
        shared_data->queue = mmap(NULL, sizeof(N_buff), PROT_READ | PROT_WRITE,
            MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    #else
        shared_data->queue = mmap(NULL, sizeof(*shared_data->queue) * N_buff,
            PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    #endif
    // NOTE: sizeof(char) is _always_ 1
        for (int i = 0; i < N_buff; i++) {
            shared_data->queue[i] = mmap(NULL, 70,
                PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
        }
    
        shared_data->queue_start = 0;
        shared_data->queue_after_last = 0;
        shared_data->queue_count = 0;
        shared_data->prod_status = 1;
    
        sem_init(&shared_data->P, 1, N_buff);
        sem_init(&shared_data->C, 1, 0);
        sem_init(&shared_data->M, 1, 1);
    }
    

    Here's some cleaned up code for a single mmap [I've compiled but not tested it]:

    #include <stdio.h>
    #include <stdlib.h>
    #include <semaphore.h>
    #include <sys/sem.h>
    #include <sys/mman.h>
    
    #define PERQUEUE 70
    
    struct shared {
        sem_t P;
        sem_t C;
        sem_t M;
        int prod_status;
        char **queue;
        int buffer_size;
        int queue_start;
        // pointing to buffer index after the last element in the buffer
        int queue_after_last;
        int queue_count;
    };
    
    int
    main(int agrc, char *argv[])
    {
        int N_buff = atoi(argv[1]);
        struct shared *shared_data = NULL;
        size_t total_size = 0;
    
        total_size += sizeof(struct shared);
        total_size += sizeof(*shared_data->queue) * N_buff;
        total_size += sizeof(PERQUEUE * N_buff);
    
        void *mapbase = mmap(NULL, total_size, PROT_READ | PROT_WRITE,
            MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
        void *mapcur = mapbase;
    
        shared_data = mapcur;
        mapcur += sizeof(struct shared);
    
        // printf("hello\n");
        shared_data->buffer_size = N_buff;
    
        shared_data->queue = mapcur;
        mapcur += sizeof(*shared_data->queue) * N_buff;
    
        for (int i = 0; i < N_buff; i++) {
            shared_data->queue[i] = mapcur;
            mapcur += PERQUEUE;
        }
    
        shared_data->queue_start = 0;
        shared_data->queue_after_last = 0;
        shared_data->queue_count = 0;
        shared_data->prod_status = 1;
    
        sem_init(&shared_data->P, 1, N_buff);
        sem_init(&shared_data->C, 1, 0);
        sem_init(&shared_data->M, 1, 1);
    
        // stuff ...
    
        munmap(mapbase,total_size);
    }