【问题标题】:C - shared memory - dynamic array inside shared structC - 共享内存 - 共享结构内的动态数组
【发布时间】:2013-01-11 13:54:27
【问题描述】:

我正在尝试共享这样的结构
示例:

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

【问题讨论】:

  • shmat 应该适合你。
  • 其实没有,那个结构有一个指针,一个指针只会有局部意义,不应该被共享。如果要将数据放在共享内存中,则必须将指针转换为某个偏移量
  • @nos,你当然是对的;您需要使用 shmaddr 附加来自“目标”进程的段,替换“原始”指针。一般来说,我会将结构打包到连续内存中,以提高效率并避免替换每个指针
  • 正如@nos 所说,带有指针的结构不适用于指针类型。这些指向进程的地址空间。在现代 POSIX 系统上,有一个接口比shmget 更容易用于共享内存,即shmopen 和mmap 的组合。
  • 没有 IPC 允许您轻松共享动态分配的内存。相反,我建议传递将通过指针共享的信息,同时使用类似缓冲区的方法。并且 mmap 应该用于基于文件的 I/O,主要是只读段。

标签: c memory dynamic malloc shared


【解决方案1】:

首先,要共享int *a 字段指向的内容,您需要复制与其相关的整个内存。因此,您需要一个至少可以容纳size_t shm_size = sizeof(struct ex) + get_the_length_of_your_ex(); 的共享内存。

从现在开始,既然你提到了 shmget 和 shmat,我就假设你运行的是 Linux 系统。
第一步是创建共享内存段。如果您可以确定int *a 内容大小的上限,那将是一件好事。这样您就不必一遍又一遍地创建/删除共享内存段。但是,如果您这样做,则需要额外的开销来说明实际数据需要多长时间。我假设一个简单的size_t 就可以达到这个目的。

然后,在您创建细分后,您必须正确设置数据以使其包含您想要的内容。请注意,虽然内存段的物理地址始终相同,但在调用shmat 时,您将获得virtual 指针,这些指针仅在调用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 来完成。

【讨论】:

    【解决方案2】:

    使用malloc() 分配给指针的内存是该进程私有的。因此,当您尝试访问另一个进程(分配它的进程除外)中的指针时,您可能会访问无效的内存页面或映射到另一个进程地址空间的内存页面。因此,您很可能会遇到段错误。

    如果您正在使用共享内存,则必须确保要向其他进程公开的所有数据都“在”共享内存段中,而不是进程的私有内存段。

    您可以尝试将数据保留在内存段中的指定偏移量处,该偏移量可以在编译时具体定义或放置在共享内存段中某个已知位置的字段中。

    例如: 如果你这样做

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

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

    希望这会有所帮助。

    【讨论】:

    【解决方案3】:

    您是在 Windows 还是 Linux 中工作? 无论如何,您需要的是memory mapped file。此处包含代码示例的文档,

    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

    【讨论】:

      【解决方案4】:

      您需要使用共享内存/内存映射文件/您的操作系统为您提供的任何内容。 通常,进程之间的 IPC 和共享内存非常依赖于操作系统,尤其是在 C 之类的低级语言中(高级语言通常有相应的库 - 例如,甚至 C++ 也使用 boost 支持它)。 如果你在 Linux 上,我通常使用 shmat 来处理少量,而 mmap (http://en.wikipedia.org/wiki/Mmap) 来处理大量。 在 Win32 上,有很多方法;我更喜欢的通常是使用页面文件支持的内存映射文件 (http://msdn.microsoft.com/en-us/library/ms810613.aspx)

      另外,您需要注意在数据结构中使用这些机制的位置:如 cmets 中所述,如果不使用预防措施,您在“源”进程中的指针在“目标”进程中无效,并且需要替换/调整(IIRC,来自 mmap 的指针已经可以(映射);至少,在 windows 指针下,您可以从 MapViewOfFile 中取出)。

      编辑:来自您编辑的示例: 你在这里做什么:

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

      (其他进程)

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

      是正确的,但是您需要为每个指针执行此操作,而不仅仅是指向结构的“主”指针。例如。你需要做的:

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

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

      【讨论】:

      • 你能给我一个使用 shmat 的例子吗,即使结构有一个带指针的字段也可以工作?我不能让它工作,我(当然)得到一个分段错误。
      • @ChiP 你如何首先分享“ex”结构?你能和我们分享你的代码吗?
      • 这是在浪费内存,因为每个段至少有 1 页内存(通常为 4K)。 + 达到每个进程的最大共享内存段数后,操作系统将拒绝分配。
      • 甚至 64k,取决于处理器架构、操作系统设置等。我不明白它为什么会“浪费”:如果您需要在进程之间共享内存,这就是 thr os 给您的.如果您需要多次执行此操作,您可以分配页面,然后跟踪其中使用的部分,就像 malloc 一样。但这仅在您需要时才有用,并且无论如何都超出了此问题的范围
      【解决方案5】:

      转换结构体:

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

      然后在父进程上:

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

      它应该可以工作。

      一般来说,在 c 结构中使用动态数组是很困难的,但通过这种方式,您可以分配适当的内存(这也适用于 malloc:How to include a dynamic array INSIDE a struct in C?)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-11-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多