Linux: Translate virtual to physical addresses in user space


Each memory allocated in a process use a special address system called virtual address. This is a special address used inside the process space. However, sometime, could be necessary to know the real physical address in system memory for different purposes in user space. Here a short snippet showing how to do.



At first some note. Operating systems not keep allocated memory always in the same memory space but continuously change by swapping to disk or moving for optimization purposes. This mean if you try to get the physical address of some allocated memory in your process you are not sure it will remain the same for the entire time of execution unless to lock it. Making a pratical example after allocating some memory with malloc() if you want to be sure the reserved memory will not be moved or paged you have to lock it by using the mlock() call as showed:

const int size = 100;
void *memory;

memory = malloc(size);
mlock(memory , size);

Please note also you have to start use your memory (read or write) only AFTER locked it. This for avoid OS swapping system and force it to assign us a fixed memory page. If you don't want to get additional info about the way the translation happen the ready to copy function to make translation from virtual to physical addresses is the following, you can copy and past on your code. On the contrary, if you want to get a bit of info you can proceed to read after the snippet. Only note an important point of this code you have to know. Basically all the code is fully portable, the only value can change system by system is the page_shift value. This a label inside the kernel that usually is 12 but can change so in case of problems you have to check this value on your kernel sources (additional info here):

uint64_t get_physcial_address(void *virtual_address)
{
    const uint64_t page_size = getpagesize();
    const uint64_t page_length = 8;
    const uint64_t page_shift = 12;
    uint64_t page_offset, page_number;
    int pagemap;

    pagemap = open("/proc/self/pagemap", O_RDONLY);
    if(pagemap < 0)
    {
        return 0;
    }

    page_offset = (((uint64_t)virtual_address) / page_size * page_length);
    if(lseek(pagemap, page_offset, SEEK_SET) != page_offset)
    {
        close(pagemap);
        return 0;
    }

    page_number = 0;
    if(read(pagemap, &page_number, sizeof(page_number)) != sizeof(page_number))
    {
        close(pagemap);
        return 0;
    }
    page_number &= 0x7FFFFFFFFFFFFFULL;

    close(pagemap);

    return ((page_number << page_shift) + (((uint64_t)virtual_address) % page_size));
}

Basically the trick is to use the pagemap kernel file for know how the kernel is currently managing the pages of the process (additional info here). We set the device pointer to the corresponding page into the map and then we calculate the offset of the physical addresses where our allocated memory is stored. As obvious you need to have the root privileges to access pagemap file. The big & (logical and) with 0x7FFFFFFFFFFFFF is because each page have 64 bits of information but, for our purpose, we are interested only in the first 57 bits.

Comments

Popular posts from this blog

Access GPIO from Linux user space

Android: adb push and read-only file system error

Tree in SQL database: The Nested Set Model