使用mmap with hint address是一个全局变量的地址并且MAP_FIXED

Using mmap with hint address is the address of a global variable and MAP_FIXED

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

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

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),并且由于 OS 总是将映射大小四舍五入为页面大小的倍数,所有字节在上舍入区域中被初始化为 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 

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

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

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

您可以向 SharedMemory 添加 4096 字节的填充,因此它总是大于 4096 字节,然后不共享最后一页(可能与其他全局变量重叠)。

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

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

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

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

#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.

中这样是安全的