C语言 - 共享内存 - 在共享结构体中使用动态数组

16
我正在尝试共享这样的结构体,例如:
typedef struct {
    int* a;
    int b;
    int c;
} ex;

在进程之间,问题在于当我使用malloc初始化'a'时,它会变成私有的堆区域,属于执行此操作的进程(或者至少我认为是这样)。是否有一种方法可以创建一个使用shmget、shmat的共享内存来处理这个结构体?

编辑:我正在Linux上工作。
编辑:我有一个初始化缓冲区的进程,就像这样:

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);

另一个进程可以像这样连接到共享内存:

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

然后从a中获取一个元素,在这种情况下是位置1上的元素

int i = get_el(e, 1);

2
shmat 应该适用于您。 - askmish
3
实际上不行,那个结构体有一个指针,而指针只会具有本地意义,不应该被共享。如果你想将这些数据放入共享内存中,指针必须转换为某个偏移量。 - nos
@nos,当然你是对的;你需要使用shmaddr附加“目标”进程的段,替换“原始”指针。一般来说,我会将结构体打包到连续的内存中,以提高效率并避免替换每个指针。 - Lorenzo Dematté
没有IPC可以让您轻松地共享动态分配的内存。相反,我建议使用类似缓冲区的方法通过指针传递要共享的信息。而且,对于基于文件的I / O,应该使用mmap来处理主要是只读的段。 - askmish
虽然没有明确的文档,但是这篇文章应该会有所帮助:https://dev59.com/4HVC5IYBdhLWcg3wixw0 - askmish
显示剩余2条评论
5个回答

6
首先,要分享您的 int *a 字段指向的内容,您需要复制与此相关的所有内存。因此,您需要一个共享内存,其大小至少为 size_t shm_size = sizeof(struct ex) + get_the_length_of_your_ex();
从现在开始,由于您提到了shmget和shmat,我假设您运行的是Linux系统。
第一步是创建共享内存段。如果您可以确定上限大小,则最好确定 int *a 内容的大小。这样就不必一遍又一遍地创建/删除共享内存段。但如果这样做,则需要额外的开销来说明实际数据的长度。对于此目的,我将假定一个简单的 size_t 就足够了。
然后,在您创建段之后,必须正确设置数据以使其保持所需内容。请注意,虽然内存段的物理地址始终相同,但调用 shmat 时,您将获得 虚拟 指针,这些指针仅在调用 shmat 的进程中可用。下面的示例代码应该能给您一些技巧。
#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;
}

请原谅我的懒惰,为了优化空间,在共享内存中完全不使用“struct ex”中的“int *a”指针的值时,最好不要复制它的值。但是我省略了额外的代码来处理此问题(以及一些指针检查,例如“p_ex”参数的完整性)。

但是,完成后,您必须找到一种方法在进程之间分享shm ID。这可以使用套接字、管道等方式完成...或者使用相同输入的ftok


3
使用malloc()函数分配给指针的内存仅属于该进程私有。因此,如果您尝试在另一个进程(非分配它的进程)中访问该指针,则很可能会访问无效的内存页或映射到另一个进程地址空间的内存页。因此,您很可能会收到segmentation fault错误。
如果您正在使用共享内存,必须确保您要公开给其他进程的所有数据都位于共享内存段中,而不是进程的私有内存段中。
您可以尝试将数据留在内存段的特定偏移量处,该偏移量可以在编译时明确定义,或者放置在已知位置的共享内存段中的字段中。
例如: 如果您正在执行以下操作 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);

另一种方法是使用共享内存的方式来传递信息,而不是使用动态分配的指针,可以采用固定大小的缓冲区。

希望这有所帮助。


你能提供一些关于如何在共享内存段中保留数据的信息吗? - Eun
@Eun:http://beej.us/guide/bgipc/output/html/multipage/shm.html 这个链接有一个很好的演示,展示了如何很好地使用共享内存。 - askmish
上面的链接在 beej.us 上已经无法使用了。 - user4252523

1
你需要使用共享内存/内存映射文件/操作系统提供的其他方法。通常情况下,进程间通信和共享内存在低级语言(如C)中非常依赖于操作系统(较高级别的语言通常具有库来处理这个问题,例如,即使是C ++也支持使用boost进行此操作)。如果你在Linux上,我通常使用shmat处理小数据量,对于大数据量则使用mmap(http://en.wikipedia.org/wiki/Mmap)。在Win32上,有很多方法;我通常使用基于页面文件的内存映射文件 (http://msdn.microsoft.com/en-us/library/ms810613.aspx)。

另外,你需要注意在数据结构中使用这些机制的位置:如评论所述,在没有采取预防措施的情况下,在“源”进程中拥有的指针在“目标”进程中无效,并且需要替换/调整(据我所知,来自mmap的指针已经是OK(映射)的; 至少,在Windows下,你从MapViewOfFile得到的指针是OK的)。

编辑:根据你的修改后示例,你所做的是:

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

(其他进程)

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

是正确的,但你需要为每个指针都这样做,而不仅仅是指向结构体的“main”指针。例如,你需要执行:

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

不要使用malloc函数创建数组。然后,在另一个进程中,您还需要对指针进行shmget/shmat操作。这就是为什么在注释中,我说我通常更喜欢打包结构体:这样我就不需要为每个指针执行这些操作。


你能给我一个使用shmat的例子吗?即使结构体有一个指针字段也能正常工作?我无法让它正常工作,当然会出现分段错误。 - ChiP
@ChiP,你首先如何分享“ex”结构体?你能与我们分享你的代码吗? - Lorenzo Dematté
这将浪费内存,因为每个段至少占用1页内存(通常为4K)。而且当进程达到最大共享内存段数时,操作系统将拒绝分配。 - Eun
甚至可能是64k,取决于处理器架构、操作系统设置等。我不明白为什么这会“浪费”:如果你需要在进程之间共享内存,那就是操作系统提供给你的。如果你需要多次执行此操作,你可以分配页面,然后跟踪其中使用的部分,就像malloc一样。但这只有在需要时才有用,而且超出了本问题的范围。 - Lorenzo Dematté

1

0

转换结构体:

typedef struct {
     int b;
     int c;
     int a[];
} ex;

然后在父进程中:

int mid = shmget(key, sizeof(ex) + arraysize*sizeof(int), 0666);

它应该可以工作。

一般来说,在C语言中,使用结构体内的动态数组比较困难,但是通过这种方式,您可以分配适当的内存(这也适用于malloc:如何在C语言中包含一个动态数组 INSIDE a struct?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接