0

I have 2 or more processes accessing to a shared memory. I want to create a global variable in each process and then mapping the address of this variable to the shared memory using mmap API with MAP_FIXED flag. So that, when reading/ writing to this shared memory, I just need to access to the global variable ( the same way as we share global variable between threads, but here I would like to shared global variable between processes).

I define the global variable in each process as below:

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

Then using mmap to map the shared memory to this global variable:

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

However, if sizeof(gstSharedMemory) is not multiple of PAGE_SIZE (4kb), and because the OS will always round up the map size to multiple of page size, all the bytes in the rounded-up region are initialized to 0. And it may cause the data of other global variable (for example: giOtherVar) to become Zero if their address is within this rounded-up region.

To overcome this situation, I use a byte array to backup the rounded-up region and recover it as below:

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

And finally, I access to shared memory like this:

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

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

My question is: Is there any potential problem with this implementation?

Editted: Thank to @None and his idea, I define a macro as below to make my struct aligned and rounded up to PAGE_SIZE, but at the same time, still provide the actual size of the struct if I need:

#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 
  • 1
    Why do you need to use `MAP_FIXED`? What advantage do you think that gives you? Why not just let the OS decide which address to use? – kaylum Jul 24 '20 at 12:41
  • My project is to migrate source code from vxWorks (task communication) to Linux (process communication). The global variable between tasks become shared memory between processes. So I want to use MAP_FIXED to reuse old source code (accessing to global variables) – Huyết Công Tử Jul 24 '20 at 13:07
  • Why dont you align gstSharedMemory to be a multiple of the pagesize? Attach the aligned attribute to the struct definition and it should work. If you want you can put the "shared memory" inside an extra section as well. – Konstantin W Jul 24 '20 at 13:26
  • Have you determined **why** the vxWorks version uses `MAP_FIXED`? And if that reasoning even applies to Linux? I **strongly** suspect what you are trying to do isn't going to replicate the likely reasoning for using `MAP_FIXED` in vxWorks, which means it won't work on Linux. – Andrew Henle Jul 24 '20 at 13:30
  • To be more clear: you are using the alignment attribute wrong. It should be `typedef struct __attribute__((aligned(4096)))`. With this there is no need for a backup array. I have no experience with vxWorks works, so I can't tell anything about unexpected behavior, but it should work (TM). – Konstantin W Jul 24 '20 at 13:39
  • man mmap says the address must be a multiple of the page size, so this is not supported. Second, most likely the part of the page you are trying to protect is now mapped into all of the processes, so can be clobbered by their accesses of variables also in that space. – stark Jul 24 '20 at 13:52
  • Usually you would just ***not*** use MAP_FIXED, and let mmap choose where to allocate the memory, and make a global pointer that points to the memory. – user253751 Jul 24 '20 at 14:05
  • You say making it a pointer would require a massive code change, but can you just `#define gstSharedMemory (*gpstSharedMemory)`? – user253751 Jul 24 '20 at 14:06
  • @AndrewHenle It is not vxWorks version which uses MAP_FIXED. The vxWorks version just a normal multi-task application. These tasks use global variable to share data. When I migrate the application to Linux, they become processes and shared memory. – Huyết Công Tử Jul 24 '20 at 15:46
  • @KonstantinW Why is there no need for a backup array? the attribute `__attribute__((aligned(4096)))` only makes sure the variable start address is aligned but the size of the variable is not multiple of page size. Is it right? – Huyết Công Tử Jul 25 '20 at 02:12
  • @KonstantinW Sorry for my misunderstanding. It is because I used the attribute incorrectly. I checked again and it does make the size of the variable multipel of page size – Huyết Công Tử Jul 25 '20 at 02:54

3 Answers3

3

Make sure the shared memory structure is both aligned to and sized as a multiple of page size:

#include <stdlib.h>

#ifndef  PAGE_SIZE
#define  PAGE_SIZE  4096
#endif

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

At run time, before mapping the shared memory, verify it first:

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

While this is a hack, at least this way it is safe, in Linux.

None
  • 281
  • 1
  • 3
  • Putting extra space (byte array) to the shared memory structure to make it multiple of PAGE_SIZE is a straight-forward way to avoid rounded-up region. But I wonder if there is way not to padding the struct but keep the original struct definition? Sometimes, I want to get the size of the struct and `sizeof` should give me the actual size of the struct. – Huyết Công Tử Jul 24 '20 at 16:06
  • 2
    @HuyếtCôngTử: the GCC `aligned` [type attribute](https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attributes.html) does indeed both align the start address, as well as adjust the size to a multiple of, the desired alignment. Relying on C structure packing rules, one *can* define a structure type without the alignment, and another that begins with the same members, but includes optional padding at end (either explicit, or implicit via the type attribute). The latter one is used for the variable, and the former one for finding the actual size. [...] – None Jul 24 '20 at 16:51
  • 1
    [...] The issue with that approach is that the two structures must have compatible initial members, and it is too easy to modify one and forget to update the other. To avoid this, in the rare occasion I've done this (for quick and dirty testing only, I promise! It *is* a hack, after all), I've put the members in a separate `#include` file, and `#include` it within the two structure definitions. – None Jul 24 '20 at 16:53
  • Really? I do not think the GCC `aligned` adjust the size to multiple of PAGE_SIZE. Because as I described in the post, although I already use the attriute when define shared memory variable, the OS still cause the data of other global variable (for example: `giOtherVar`) to become Zero if their address is within this rounded-up region. That is the region why I have to use a backup byte array. – Huyết Công Tử Jul 25 '20 at 02:01
  • Sorry for my lack of knowledge. It is because I useed the attribute `aligned ` incorrectly. I checked again and see that it indeed both align the start address and ajust the size to a multiple of PAGE_SIZE. Thanks. – Huyết Công Tử Jul 25 '20 at 02:51
  • @HuyếtCôngTử: No worries! You used a **variable** attribute, whereas I used a **type** attribute. The reason there is a difference, is because of **arrays**. For an array of aligned members to work, the type must be a multiple of the alignment. On the other hand, if you just align a variable (of any type, even an array type), you only align the variable, or the start of the array, not each element of that array. That is why there is such a big difference when using `__attribute__((aligned))` as a type attribute and as a variable attribute. Here, you *need* to use the type attribute. – None Jul 25 '20 at 10:59
  • Thank you. Your idea inspires me to create a macro to define the struct. Please see my edit on the original post. – Huyết Công Tử Jul 28 '20 at 07:35
1

Yes, the potential problem is that giOtherVar is now shared as well, because the entire page is shared.

The normal way to do this is to not use MAP_FIXED, and let mmap choose a location for you, and store the pointer. You say you cannot do this because it would be a massive code change.

There's probably a way to use a linker script to force gstSharedMemory to be on a page by itself, but linker scripts are tricky.

You could add 4096 bytes of padding to SharedMemory so it's always bigger than 4096 bytes, and then not share the last page (which could overlap other global variables).

You could add an unused 4096 byte array after gstSharedMemory and hope that the compiler will put it right after gstSharedMemory.

You could make the next variable also 4096-byte aligned and hope the compiler doesn't decide to put other variables in the gap.

... or you could just use the pointer design, then #define gstSharedMemory (*gpstSharedMemory) so that you don't have to change all your code.

user253751
  • 57,427
  • 7
  • 48
  • 90
  • Can you explain more detail about your point that "the potential problem is that `giOtherVar` is now shared as well, because the entire page is shared"? – Huyết Công Tử Jul 25 '20 at 02:03
  • Because in my understanding, only the data within map size ( `sizeof` shared memory) is synchronized to "file" (shared memory), therefore is shared with other processes. The rounded up region is not shared if all of my processes use mmap with the length is `sizeof` shared memory. Is it correct? – Huyết Công Tử Jul 25 '20 at 02:08
  • 1
    @HuyếtCôngTử mmap works with pages. Either the whole page is shared, or it isn't. The reason giOtherVar is overwritten is because giOtherVar is now mapped to the file. – user253751 Jul 25 '20 at 13:14
0
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct shared_data_s {
        int a;
        int b;
} __attribute__((aligned(4096))) shared_data_t;


shared_data_t g_data;
shared_data_t *g_data_ptr;

void child()
{
        printf("child set g_data to 1/2\n");
        g_data.a = 1;
        g_data.b = 2;
        sleep(2);
        printf("child visit g_data a=%d, b=%d\n", g_data.a, g_data.b);
}

void parent()
{
        sleep(1);
        printf("parent visit g_data a=%d, b=%d\n", g_data.a, g_data.b);
        g_data.a = 10;
        g_data.b = 20;
        sleep(3);
}

int main(int argc, char *argv[])
{

        g_data_ptr = mmap(&g_data, sizeof(g_data), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
        printf("%s: size=%d, &g_data=%p, g_data_ptr=%p\n", __func__, sizeof(g_data), &g_data, g_data_ptr);

        pid_t pid;
        pid = fork();
        if (pid == 0) {
                child();
        } else {
                parent();
        }

        return 0;
}
Along
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 16 '22 at 19:05