【问题标题】:Using mmap with hint address is the address of a global variable and MAP_FIXED使用带有提示地址的 mmap 是全局变量的地址和 MAP_FIXED
【发布时间】:2020-11-14 07:43:29
【问题描述】:

我有 2 个或更多进程访问共享内存。 我想在每个进程中创建一个全局变量,然后使用带有 MAP_FIXED 标志的 mmap API 将该变量的地址映射到共享内存。 因此,在读取/写入此共享内存时,我只需要访问全局变量(与我们在线程之间共享全局变量的方式相同,但这里我想在进程之间共享全局变量)。

我在每个进程中定义全局变量如下:

typedef struct     // This struct define the shared memory area
{
   int data1;
   int data2;
   // ...
} SharedMemory;

// the following attribute (supported by GCC) make the start address of the variable aligned to 4KB (PAGE_SIZE)
 __attribute__((aligned(4096))) SharedMemory gstSharedMemory; // shared global variable
 int giOtherVar = 10;                                              // Another normal global variable

然后使用 mmap 将共享内存映射到这个全局变量:

void* lpShmAddr = mmap(&gstSharedMemory,
                        sizeof(gstSharedMemory),
                        PROT_READ | PROT_WRITE,
                        MAP_SHARED | MAP_FIXED,
                        iFd,                        // File descriptor to the shared memory
                        0);

但是,如果sizeof(gstSharedMemory) 不是 PAGE_SIZE (4kb) 的倍数,并且由于操作系统将始终将映射大小四舍五入为页面大小的倍数,则四舍五入区域中的所有字节都将初始化为 0。 如果其他全局变量(例如:giOtherVar)的地址在这个四舍五入的区域内,可能会导致其数据为零。

为了克服这种情况,我使用一个字节数组来备份四舍五入的区域并恢复它,如下所示:

unsigned char byBkupShm[PAGE_SIZE]    =  { 0 } ;    
memcpy(&gbyBkupShm[0], 
      ((unsigned char*)&gstSharedMemory+ sizeof(gstSharedMemory)), 
       PAGE_SIZE - (sizeof(gstSharedMemory)% PAGE_SIZE));
void* lpShmAddr = mmap(&gstSharedMemory,
                        sizeof(gstSharedMemory),
                        PROT_READ | PROT_WRITE,
                        MAP_SHARED | MAP_FIXED,
                        iFd,                        // File descriptor to the shared memory
                        0);
 memcpy( ((unsigned char*)&gstSharedMemory+ sizeof(gstSharedMemory)), 
          &byBkupShm[0],
          PAGE_SIZE - (sizeof(gstSharedMemory)% PAGE_SIZE));

最后,我像这样访问共享内存:

// Write to shared memory:
gstSharedMemory.data1 = 5;

// Read from shared memory;
printf("%d", gstSharedMemory.data1);

我的问题是:这个实现有什么潜在的问题吗?

已编辑: 感谢@None 和他的想法,我定义了一个如下宏来使我的结构对齐并向上舍入到 PAGE_SIZE,但同时,如果我需要,仍然提供结构的实际大小:

#define PAGE_SIZE       (4 * 1024)                                  // Page Size: 4KB
#define SHM_REG          __attribute__((aligned(PAGE_SIZE)))        // Aligned to 4KB boundary

#define DEFINE_SHM( structName_, shmSizeVar_, structContent_)       \
typedef struct SHM_REG structContent_ structName_;                  \
int shmSizeVar_ = sizeof(struct structContent_);  



// Using

DEFINE_SHM(
    MySharedMemory,                 // Struct Name of shared memory
    giSizeOfMySharedMemory,         // Global Variable 
    {
        int a;
        int b;
        char c;
    }
);
    
printf("Rounded Size: %d\n", sizeof(MySharedMemory));  // = 4096
printf("Acutal Size: %d\n", giSizeOfMySharedMemory);   // = 12 

【问题讨论】:

  • 为什么需要使用MAP_FIXED?你认为这会给你带来什么好处?为什么不让操作系统决定使用哪个地址?
  • 我的项目是将源代码从vxWorks(任务通信)迁移到Linux(进程通信)。任务之间的全局变量成为进程之间的共享内存。所以我想用MAP_FIXED来重用旧的源代码(访问全局变量)
  • 为什么不将 gstSharedMemory 对齐为页面大小的倍数?将对齐的属性附加到结构定义,它应该可以工作。如果你愿意,你也可以将“共享内存”放在一个额外的部分中。
  • 你确定为什么 vxWorks版本使用MAP_FIXED了吗?如果这种推理甚至适用于 Linux?我强烈怀疑您正在尝试做的事情不会复制在 vxWorks 中使用 MAP_FIXED 的可能原因,这意味着它无法在 Linux 上运行。
  • 更清楚一点:您使用的对齐属性错误。应该是typedef struct __attribute__((aligned(4096)))。这样就不需要备份阵列了。我没有使用 vxWorks 作品的经验,所以我无法说出任何关于意外行为的信息,但它应该可以工作 (TM)。

标签: c linux process shared-memory mmap


【解决方案1】:

确保共享内存结构与页面大小对齐且大小为页面大小的倍数:

#include <stdlib.h>

#ifndef  PAGE_SIZE
#define  PAGE_SIZE  4096
#endif

typedef struct __attribute__((aligned (PAGE_SIZE))) {
    /*
     * All shared memory members
    */
} SharedMemory;

在运行时,在映射共享内存之前,先验证一下:

SharedMemory  blob;

    if (PAGE_SIZE != sysconf(_SC_PAGESIZE)) {
        ABORT("program compiled for a different page size");
    } else
    if (sizeof blob % PAGE_SIZE) {
        ABORT("blob is not sized properly");
    } else
    if ((uintptr_t)(&blob) % PAGE_SIZE) {
        ABORT("blob is not aligned properly");
    } else
    if (MAP_FAILED == mmap(...)) {
        ABORT("could not map shared memory over blob");
    }

虽然这是一种 hack,但至少在 Linux 中它是安全的。

【讨论】:

  • 将额外的空间(字节数组)放入共享内存结构以使其成为 PAGE_SIZE 的倍数是避免向上取整区域的直接方法。但我想知道是否有办法不填充结构但保留原始结构定义?有时,我想获取结构的大小,sizeof 应该给我结构的实际大小。
  • @HuyếtCôngTử:GCC aligned type attribute 确实对齐起始地址,并将大小调整为所需对齐的倍数。依赖于 C 结构打包规则,一个 可以 定义一个没有对齐的结构类型,另一个以相同的成员开始,但在末尾包含可选的填充(显式或通过 type 属性隐式) .后一个用于变量,前一个用于查找实际大小。 [...]
  • [...] 这种方法的问题是这两个结构必须具有兼容的初始成员,而且很容易修改一个而忘记更新另一个。为了避免这种情况,在极少数情况下我会这样做(我保证仅用于快速和肮脏的测试!毕竟它一个hack),我将成员放在单独的@ 987654326@文件和#include它在两个结构定义中。
  • 真的吗?我不认为 GCC aligned 将大小调整为 PAGE_SIZE 的倍数。因为正如我在帖子中所描述的,虽然我在定义共享内存变量时已经使用了属性,但是如果其他全局变量(例如:giOtherVar)的地址在这个四舍五入之内,操作系统仍然会导致它们的数据为零地区。这就是我必须使用备份字节数组的区域。
  • 对不起,我缺乏知识。这是因为我错误地使用了属性aligned 。我再次检查,发现它确实既对齐了起始地址,又将大小调整为 PAGE_SIZE 的倍数。谢谢。
【解决方案2】:

是的,潜在的问题是 giOtherVar 现在也被共享了,因为整个页面都是共享的。

执行此操作的正常方法是使用MAP_FIXED,让mmap 为您选择一个位置,并存储指针。你说你不能这样做,因为这将是一个巨大的代码更改。

可能有一种方法可以使用linker script 来强制gstSharedMemory 单独出现在页面上,但是链接器脚本很棘手。

您可以将 4096 字节的填充添加到 SharedMemory,使其始终大于 4096 字节,然后不共享最后一页(这可能与其他全局变量重叠)。

您可以在gstSharedMemory 之后添加一个未使用的 4096 字节数组,并且希望编译器会将其放在gstSharedMemory 之后。

您可以使 next 变量也对齐 4096 字节,并希望编译器不会决定将其他变量放在间隙中。

...您可以只使用指针设计,然后使用#define gstSharedMemory (*gpstSharedMemory),这样您就不必更改所有代码。

【讨论】:

  • 您能否更详细地解释您的观点“潜在问题是 giOtherVar 现在也被共享了,因为整个页面都是共享的”?
  • 因为在我的理解中,只有在映射大小(sizeof共享内存)内的数据会同步到“文件”(共享内存),因此会与其他进程共享。如果我的所有进程都使用长度为sizeof 共享内存的 mmap,则不共享向上取整的区域。对吗?
  • @HuyếtCôngTử mmap 适用于页面。整个页面要么共享,要么不共享。 giOtherVar 被覆盖的原因是 giOtherVar 现在已映射到文件。
猜你喜欢
  • 1970-01-01
  • 2021-09-14
  • 2019-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-06
相关资源
最近更新 更多