Accessing two discontinuous memory blocks as a single continuous block, in C?
Asked Answered
M

1

6

In Linux user space: suppose that I allocated a 3MiB continuous memory ABC, where A, B and C are 1MiB each. Is there a way to access AC as a single continuous 2MiB memory somehow (a kind of user space MMU)?

Background: my scenario is to model an ASIC chip in C. The chip has a 3MiB memory, and two components in the chip can access the same memory hardware, but with different address maps. To model that in C, one way could be duplicating the 3MiB memory for the two components, and adding some synchronization mechanism between the two memory copies. The other way could be just having a single copy of 3MiB memory, one component accesses the memory as it is, and for the other component, adding a kind of wrapper to do the address translation. I just want to know if there is a neater way, avoiding the memory duplication and the address translation wrapper...

PS: according Maxim's answer, I wrote a simple test program, and it just works. It seems I should not use MAP_ANONYMOUSE flag for my purpose. Also, according to mmap() manual, for MAP_FIXED to work properly, the alignment of C should be multiple of PAGE_SIZE on the target platform (mine x86_64 Ubuntu). I paste the code below just in case...

#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

#define SZ_A     (64 * 1024)   // A
#define SZ_B    (204 * 1024)   // B
#define SZ_C      (4 * 1024)   // C
#define SZ_ABC  (272 * 1024)   // ABC
#define SZ_AB   (268 * 1024)   // AB
#define SZ_AC    (68 * 1024)   // AC

const char shm_name[] = "/shm_name1";
unsigned char* p_abc = NULL;
unsigned char* p_ac = NULL;

#define MMAP_PROT  (PROT_READ | PROT_WRITE)

#define USE_MAP_ANONYMOUSE  0
#if USE_MAP_ANONYMOUSE
#define MMAP_FLAG        (MAP_SHARED | MAP_ANONYMOUS)
#define MMAP_FLAG_FIXED  (MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED)
#else
#define MMAP_FLAG        (MAP_SHARED)
#define MMAP_FLAG_FIXED  (MAP_SHARED | MAP_FIXED)
#endif

int main(void)
{
    int fd = - 1;
    void* p = NULL;
    fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == - 1) {
        printf("shm_open() fail\n");
        return - 1;
    }

    if (0 != ftruncate(fd, SZ_ABC)) {
        printf("ftruncate() fail\n");
        return - 1;
    }

    if (0 != shm_unlink(shm_name)) {
        printf("shm_unlink() fail\n");
        return - 1;
    }

    p_abc = (unsigned char*)mmap(NULL, SZ_ABC, MMAP_PROT, MMAP_FLAG, fd, 0);
    if (p_abc == (unsigned char*) -1) {
        printf("p_abc = mmap(NULL) fail\n");
        p_abc = NULL;
        goto EXIT;
    }

    p_ac = (unsigned char*)mmap(NULL, SZ_AC, MMAP_PROT, MMAP_FLAG, fd, 0);
    if (p_ac == (unsigned char*) -1) {
        printf("p_ac = mmap(NULL) fail\n");
        p_ac = NULL;
        goto EXIT;
    }

    p = mmap(p_ac + SZ_A, SZ_C, MMAP_PROT, MMAP_FLAG_FIXED, fd, SZ_AB);
    if (p == MAP_FAILED || p != (void*)(p_ac + SZ_A)) {
        printf("mmap(MAP_FIXED) fail\n");
        p = NULL;
        goto EXIT;
    }
    close(fd);

    printf("mmap() ok:"
            "\np_abc=0x%" PRIxPTR
            "\n p_ac=0x%" PRIxPTR
            "\n    p=0x%" PRIxPTR "\n",
            (uintptr_t)p_abc, (uintptr_t)p_ac, (uintptr_t)p);

    // test
    memset(p_abc, 0xab, SZ_AB);
    memset(p_abc + SZ_AB, 0x0c, SZ_C);
    // should be: ab, 0c
    printf("sm4: %02x, %02x\n", p_ac[0], p_ac[SZ_A]);

    memset(p_ac, 0x0c, SZ_AC);
    // should be: 0c, 0c
    printf("sm0: %02x, %02x\n", p_abc[0], p_abc[SZ_AB]);

EXIT:
    if (p) munmap(p, SZ_C);
    if (p_ac) munmap(p_ac, SZ_AC);
    if (p_abc) munmap(p_abc, SZ_ABC);

    return 0;
}


Mange answered 14/1, 2019 at 14:10 Comment(0)
D
6

suppose that I allocated a 3MiB continuous memory ABC, where A, B and C are 1MiB each. Is there a way to access AC as a single continuous 2MiB memory somehow (a kind of user space MMU)?

You can create a memory mapped file of 3MiB size with shm_open (and, optionally, immediately shm_unlink it). Then map that file into the process address space multiple times using different offsets and sizes.

One note, to map AC contiguously, you first need to mmap 2MiB of anonymous memory to reserve a contiguous block of address space. Then mmap the first half of it to A and the second half to C using MAP_FIXED flag.

Pseudo-code:

// Create an anonymous shared memory file.
int fd = shm_open(filename);
shm_unlink(filename);
ftruncate(fd, 3MiB);

// Map A and C contiguously.
void* ac = mmap(NULL, 2MiB, ..., fd, 0MiB); // maps AB part of ABC.
mmap(ac + 1MiB, 1MiB, ... | MAP_FIXED, fd, 2MiB); // remaps C part over B part (B gets unmapped).
Damp answered 14/1, 2019 at 14:26 Comment(3)
Good answer, shm_unlink takes the filename however, not the file descriptor. And you could mmap 2MB of fd directly to ac and only remap the second half, saves one call to mmap.Neddy
@Neddy You are quite right. Your suggestions have been integrated.Damp
Thank you, Maxim and Ctx. I did a simple test, and it works!Mange

© 2022 - 2024 — McMap. All rights reserved.