Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C - shared memory - dynamic array inside shared struct

i'm trying to share a struct like this
example:

typedef struct {
    int* a;
    int b;
    int c;
} ex;

between processes, the problem is that when I initialize 'a' with a malloc, it becomes private to the heap of the process that do this(or at least i think this is what happens). Is there any way to create a shared memory (with shmget, shmat) with this struct that works?

EDIT: I'm working on Linux.
EDIT: I have a process that initialize the buffer like this:

key_t key = ftok("gr", 'p');   
int mid = shmget(key, sizeof(ex), IPC_CREAT | 0666);    
ex* e = NULL;
status b_status = init(&e, 8); //init gives initial values to b c and allocate space for 'a' with a malloc
e = (ex*)shmat(mid, NULL, 0);

the other process attaches himself to the shared memory like this:

key_t key = ftok("gr", 'p');
int shmid = shmget(key, sizeof(ex), 0);
ex* e;
e = (ex*)shmat(shmid, NULL, 0);  

and later get an element from a, in this case that in position 1

int i = get_el(e, 1);
like image 968
ChiP Avatar asked Jan 28 '13 08:01

ChiP


4 Answers

First of all, to share the content pointed by your int *a field, you will need to copy the whole memory related to it. Thus, you will need a shared memory that can hold at least size_t shm_size = sizeof(struct ex) + get_the_length_of_your_ex();.

From now on, since you mentioned shmget and shmat, I will assume you run a Linux system.
The first step is the shared memory segment creation. It would be a good thing if you can determine an upper bound to the size of the int *a content. This way you would not have to create/delete the shared memory segment over and over again. But if you do so, an extra overhead to state how long is the actual data will be needed. I will assume that a simple size_t will do the trick for this purpose.

Then, after you created your segment, you must set the data correctly to make it hold what you want. Notice that while the physical address of the memory segment is always the same, when calling shmat you will get virtual pointers, which are only usable in the process that called shmat. The example code below should give you some tricks to do so.

#include <sys/types.h>
#include <sys/ipc.h>

/* Assume a cannot point towards an area larger than 4096 bytes. */
#define A_MAX_SIZE (size_t)4096

struct ex {
    int *a;
    int b;
    int c;
}

int shm_create(void)
{
    /*
     * If you need to share other structures,
     * You'll need to pass the key_t as an argument
     */
    key_t k = ftok("/a/path/of/yours");
    int shm_id = 0;
    if (0 > (shm_id = shmget(
        k, sizeof(struct ex) + A_MAX_SIZE + sizeof(size_t), IPC_CREAT|IPC_EXCL|0666))) {
        /* An error occurred, add desired error handling. */
    }
    return shm_id;
}

/*
 * Fill the desired shared memory segment with the structure
 */
int shm_fill(int shmid, struct ex *p_ex)
{
    void *p = shmat(shmid, NULL, 0);
    void *tmp = p;
    size_t data_len = get_my_ex_struct_data_len(p_ex);
    if ((void*)(-1) == p) {
        /* Add desired error handling */
        return -1;
    }
    memcpy(tmp, p_ex, sizeof(struct ex));
    tmp += sizeof(struct ex);
    memcpy(tmp, &data_len, sizeof(size_t);
    tmp += 4;
    memcpy(tmp, p_ex->a, data_len);

    shmdt(p);
    /*
     * If you want to keep the reference so that
     * When modifying p_ex anywhere, you update the shm content at the same time :
     * - Don't call shmdt()
     * - Make p_ex->a point towards the good area :
     * p_ex->a = p + sizeof(struct ex) + sizeof(size_t);
     * Never ever modify a without detaching the shm ...
     */
    return 0;
}

/* Get the ex structure from a shm segment */
int shm_get_ex(int shmid, struct ex *p_dst)
{
    void *p = shmat(shmid, NULL, SHM_RDONLY);
    void *tmp;
    size_t data_len = 0;
    if ((void*)(-1) == p) {
        /* Error ... */
        return -1;
    }
    data_len = *(size_t*)(p + sizeof(struct ex))
    if (NULL == (tmp = malloc(data_len))) {
        /* No memory ... */
        shmdt(p);
        return -1;
    }
    memcpy(p_dst, p, sizeof(struct ex));
    memcpy(tmp, (p + sizeof(struct ex) + sizeof(size_t)), data_len);
    p_dst->a = tmp;
    /*
     * If you want to modify "globally" the structure,
     * - Change SHM_RDONLY to 0 in the shmat() call
     * - Make p_dst->a point to the good offset :
     * p_dst->a = p + sizeof(struct ex) + sizeof(size_t);
     * - Remove from the code above all the things made with tmp (malloc ...)
     */
    return 0;
}

/*
 * Detach the given p_ex structure from a shm segment.
 * This function is useful only if you use the shm segment
 * in the way I described in comment in the other functions.
 */
void shm_detach_struct(struct ex *p_ex)
{
    /*
     * Here you could : 
     * - alloc a local pointer
     * - copy the shm data into it
     * - detach the segment using the current p_ex->a pointer
     * - assign your local pointer to p_ex->a
     * This would save locally the data stored in the shm at the call
     * Or if you're lazy (like me), just detach the pointer and make p_ex->a = NULL;
     */

    shmdt(p_ex->a - sizeof(struct ex) - sizeof(size_t));
    p_ex->a = NULL;
}

Excuse my laziness, it would be space-optimized to not copy at all the value of the int *a pointer of the struct ex since it is completely unused in the shared memory, but I spared myself extra-code to handle this (and some pointer checkings like the p_ex arguments integrity).

But when you are done, you must find a way to share the shm ID between your processes. This could be done using sockets, pipes ... Or using ftok with the same input.

like image 126
Rerito Avatar answered Nov 13 '22 17:11

Rerito


The memory you allocate to a pointer using malloc() is private to that process. So, when you try to access the pointer in another process(other than the process which malloced it) you are likely going to access an invalid memory page or a memory page mapped in another process address space. So, you are likely to get a segfault.

If you are using the shared memory, you must make sure all the data you want to expose to other processes is "in" the shared memory segment and not private memory segments of the process.

You could try, leaving the data at a specified offset in the memory segment, which can be concretely defined at compile time or placed in a field at some known location in the shared memory segment.

Eg: If you are doing this

char *mem = shmat(shmid2, (void*)0, 0);

// So, the mystruct type is at offset 0.
mystruct *structptr = (mystruct*)mem;

// Now we have a structptr, use an offset to get some other_type.
other_type *other = (other_type*)(mem + structptr->offset_of_other_type);

Other way would be to have a fixed size buffer to pass the information using the shared memory approach, instead of using the dynamically allocated pointer.

Hope this helps.

like image 3
askmish Avatar answered Nov 13 '22 17:11

askmish


Are you working in Windows or Linux? In any case what you need is a memory mapped file. Documentation with code examples here,

http://msdn.microsoft.com/en-us/library/aa366551%28VS.85%29.aspx http://menehune.opt.wfu.edu/Kokua/More_SGI/007-2478-008/sgi_html/ch03.html

like image 1
jpmuc Avatar answered Nov 13 '22 18:11

jpmuc


You need to use shared memory/memory mapped files/whatever your OS gives you. In general, IPC and sharing memory between processes is quite OS dependent, especially in low-level languages like C (higher-level languages usually have libraries for that - for example, even C++ has support for it using boost). If you are on Linux, I usually use shmat for small amount, and mmap (http://en.wikipedia.org/wiki/Mmap) for larger amounts. On Win32, there are many approaches; the one I prefer is usually using page-file backed memory mapped files (http://msdn.microsoft.com/en-us/library/ms810613.aspx)

Also, you need to pay attention to where you are using these mechanism inside your data structures: as mentioned in the comments, without using precautions the pointer you have in your "source" process is invalid in the "target" process, and needs to be replaced/adjusted (IIRC, pointers coming from mmap are already OK(mapped); at least, under windows pointers you get out of MapViewOfFile are OK).

EDIT: from your edited example: What you do here:

e = (ex*)shmat(mid, NULL, 0);

(other process)

int shmid = shmget(key, sizeof(ex), 0);
ex* e = (ex*)shmat(shmid, NULL, 0);

is correcty, but you need to do it for each pointer you have, not only for the "main" pointer to the struct. E.g. you need to do:

e->a = (int*)shmat(shmget(another_key, dim_of_a, IPC_CREAT | 0666), NULL, 0);

instead of creating the array with malloc. Then, on the other process, you also need to do shmget/shmat for the pointer. This is why, in the comments, I said that I usually prefer to pack the structs: so I do not need to go through the hassle to to these operations for every pointer.

like image 1
Lorenzo Dematté Avatar answered Nov 13 '22 18:11

Lorenzo Dematté