Getting information about the layout of a shared memory object

Updated: April 19, 2023

If you need to get information about the layout of a memory object (for example, to program a DMA engine or IOMMU), you can send a _MEM_OBJ_INFO message to the memory manager.

The _MEM_OBJ_INFO message lets you get the physical addresses associated with a shared memory object, if it was populated via shm_ctl() or shm_ctl_special(), without mapping it or calling mlock(). It can replace multiple calls to mem_offset().

Note: You can't use this message to get information about a shared memory object that was populated using the SHMCTL_LAZY flag or ftruncate(). Generally speaking, such an object isn't a physical memory object, and you can't guarantee that it has physical backing unless you mmap() and then mlock() it.

This message uses the following data structures, which are defined in <sys/memmsg.h>:

struct _mem_obj_info {
    _Uint16t              type;
    _Uint16t              zero;
    _Int32t               fd;
    _Uint64t              offset;
    _Uint64t              length;
};

struct _mem_obj_segment {
    _Uint64t              offset;
    _Uint64t              paddr;
    _Uint64t              length;
};

struct _mem_obj_info_reply {
    __FLEXARY(struct _mem_obj_segment, segments);
};

typedef union {
    struct _mem_obj_info                    i;
    struct _mem_obj_info_reply              o;
} mem_obj_info_t;

Allocate a message with enough space for the expected information, and then set up the input (i) part of the message as follows:

type
The type of the message, _MEM_OBJ_INFO.
zero
Not used; set this to 0.
fd
A file descriptor for the shared memory object, from a call to shm_open(),
offset
The offset where you want to start in the shared memory object.
length
The number of bytes, starting at offset, that you want to retrieve the information for. You can set this to a large number (e.g., SIZE_MAX) if you want the layout of the whole object.

Send the message to the memory manager's connection, which is identified by MEMMGR_COID. If an error occurs, you'll get an error code; otherwise the memory manager fills in the output (o) part of the message and returns the number of array members that it filled. Each member of the returned array includes the offset, physical address and length of the segment.

Here's an example of using the _MEM_OBJ_INFO message:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/memmsg.h>

int
main(void)
{
    uint64_t obj_size = 8 * __PAGESIZE;
    long i;

    // Create a shared memory object.
    int fd = shm_open("/myshmem", O_RDWR | O_CREAT | O_TRUNC, 0600);
    if (fd == -1) {
        perror("shm_open");
        return 1;
    }

    // Populate the object with physical memory.
    if (shm_ctl(fd, SHMCTL_ANON, 0, obj_size) == -1) {
        perror("shm_ctl");
        return 1;
    }

    // Allocate a message with enough space for eight segments.
    mem_obj_info_t  *msg = malloc(sizeof(struct _mem_obj_segment) * 8);
    if (msg == NULL) {
        perror("malloc");
        return 1;
    }

    // Send the message.
    msg->i.type = _MEM_OBJ_INFO;
    msg->i.zero = 0;
    msg->i.fd = fd;
    msg->i.offset = 0;
    msg->i.length = SIZE_MAX;

    long nsegs = MsgSend(MEMMGR_COID, msg, sizeof(*msg), msg, sizeof(*msg));
    if (nsegs == -1) {
        perror("MsgSend");
        return 1;
    }

    // Print the result.
    printf("Observed results:\n");
    for (i = 0; i < nsegs; i++) {
        printf("   offset=%lx paddr=%lx len=%lx\n",
               msg->o.segments[i].offset,
               msg->o.segments[i].paddr,
               msg->o.segments[i].length);
    }

    i = nsegs -1;
    if ((i >= 0) && (msg->o.segments[i].offset + msg->o.segments[i].length == obj_size)) {
       printf ("We reached the end of the shared memory object.\n");
    }
    return 0;
}