What is memory mapping in linux

Memory sharing in Linux with MMAP

MMAP is a UNIX system call that maps files into memory. It’s a method used for memory-mapped file I/O. It brings in the optimization of lazy loading or demand paging such that the I/O or reading file doesn’t happen when the memory allocation is done, but when the memory is accessed. After the memory is no longer needed it can be cleared with munmap call. MMAP supports certain flags or argument which makes it suitable for allocating memory without file mapping as well. In Linux kernel, the malloc call uses mmap with MAP_ANONYMOUS flag for large allocations.

In this article, I’ll be explaining how what mmap is and how it can be used for sharing memory in Linux. It kind of is the backbone of shared memory in Android.

Arguments and flags

mmap() creates a new mapping in the virtual address space of the calling process. If you check out the Linux kernel page for mmap you’ll see several arguments and flags. On the other hand, munmap() is used to free the allocated memory.

MMAP definition

  • The addr specifies the starting address of the allocation and if it’s passed as NULL the kernel chooses the starting address.
  • The length argument specifies the length of allocation in bytes and should be > 0 .
  • The prot argument describes the protection level
    • PROT_EXEC Pages may be executed.
    • PROT_READ Pages may be read.
    • PROT_WRITE Pages may be written.
    • PROT_NONE Pages may be not be accessed.

    The flags can be passed with bitwise OR operator and the default protection level is

    MUNMAP definition

    The munmap() system call deletes the mappings for the specified address range and causes further references to addresses within the range to generate invalid memory references. The region is also automatically unmapped when the process is terminated. On the other hand, closing the file descriptor does not unmap the region.

    • The addr is the address of allocation to free, essentially what you got from calling the mmap() . After calling munmap() , any access on the memory address shall raise SIGSEV errors.
    • The length determines the area of memory to clear. The area of memory from addr to addr + length would be freed on this call.

    Sharing memory with MMAP

    MMAP can be thought of as the core memory allocation API in Linux and several high-level constructs take advantage of this for providing various features. Linux kernel is the core of Android OS and components like ASHMEM uses MMAP in its core. ASHMEM is used for sharing memory in Android in different components like ContentProviders or Binder IPC.

    Sharing between parent and child

    This is fairly simple to visualize. A mmap allocation with MAP_SHARED flag can be accessed directly by the child process.

    This is very helpful in sharing the memory of core components in Android. All applications in Android are forked from a bare-bone process called Zygote which loads the core libraries and code required by all applications with mmap . Zygote is loaded into memory on device boot and when a user attempts to open an application for the first time the system forks Zygote and then the application logic is initialized.

    Sharing between siblings

    While it’s easy to visualize how memory can be shared in ancestry between a parent and child. The logic is very similar but involves Inter-Process Communication (IPC). Two common ways to achieve this could be:

    Without extra management layer

    The concept is similar, the two processes say Process 1 and Process 2 can communicate with each other via certain IPC technology.

    • Process 1 creates a file and allocates memory on that with MAP_SHARED flag and appropriate protection level and length. This process can write some data in the allocated memory space.
    • Process 1 shares this file descriptor with Process 2 via a certain IPC method.
    • Process 2 receives this file descriptor and calls mmap on that. So the system returns the virtual address of the same memory allocation and based on the protection levels set by Process 1 , Process 2 can read, write or execute the shared memory pages.

    However, these processes are responsible for explicitly deallocating memory, otherwise, it cannot be reused by another process in need of memory.

    With an extra management layer

    In this case, another process acts as the manager of shared memory and exposes interface or methods to allocate or retrieve memory allocations. Let’s say there is a memory manager called XMAN and exposes APIs like this:

    • Process 1 could allocate a chunk of memory using Xman_allocate() and share the Xman_allocation.fd with another process via a certain IPC mechanism.
    • Process 2 could use Xman_get() to get the same allocation and act on it.
    • Any of these processes could use the Xman_free() to explicitly free the memory.

    While the way of dealing with shared memory seems very similar with or without a manager instance, a centralized manager can abstract some memory freeing techniques thus taking away the expectation of being good citizens from the calling processes like:

    • Freeing memory after use, the Manager can take care of freeing when the calling processes die.
    • Some components like ASHMEM, support features like PINNING and UNPINNING section of memory which allows the consumer process to set which part of memory can be cleared when the system is out of free memory. This protects the consumer apps from being killed by the Low Memory Killer (LMK) when it’s reclaiming memory. ASHMEM has its process on deciding which UNPINNED memory to clear when available memory is system goes below a certain threshold.

    MISC: MMAP is faster than reading a file in blocks

    While exploring these concepts I was wondering how file-backed memory manages to be performant while file IO operation like read() is generally considered much slower than memory operations. There are a few interesting StackOverflow questions like Why mmap() is faster than sequential IO? and mmap() vs. reading blocks which answer this questions pretty well.

    But they won’t give you a short answer like — Because MMAP is magic! They are long reads.

    I wish I could add a TL;DR; answer to this question here but there isn’t one. Both mmap() and read() have their pros and cons and could be more performant in different situations. While mmap() seems like magic, it’s simply not.

    Future readings

    In the future, I intend to write about what ASHMEM is, how it works, why it was brought when MMAP existed and examples of how it’s been used in Android. Another interesting memory manager in Android is the ION memory manager which was added to Linux kernel in 2011 by a patch from Google to solve issues around large memory allocations needed by components like GPU, display, camera, etc.

    Источник

    Memory mapping¶

    Lab objectives¶

    • Understand address space mapping mechanisms
    • Learn about the most important structures related to memory management
    • address space
    • mmap()
    • struct page
    • struct vm_area_struct
    • struct vm_struct
    • remap_pfn_range
    • SetPageReserved()
    • ClearPageReserved()

    Overview¶

    In the Linux kernel it is possible to map a kernel address space to a user address space. This eliminates the overhead of copying user space information into the kernel space and vice versa. This can be done through a device driver and the user space device interface ( /dev ).

    This feature can be used by implementing the mmap() operation in the device driver’s struct file_operations and using the mmap() system call in user space.

    The basic unit for virtual memory management is a page, which size is usually 4K, but it can be up to 64K on some platforms. Whenever we work with virtual memory we work with two types of addresses: virtual address and physical address. All CPU access (including from kernel space) uses virtual addresses that are translated by the MMU into physical addresses with the help of page tables.

    A physical page of memory is identified by the Page Frame Number (PFN). The PFN can be easily computed from the physical address by dividing it with the size of the page (or by shifting the physical address with PAGE_SHIFT bits to the right).

    For efficiency reasons, the virtual address space is divided into user space and kernel space. For the same reason, the kernel space contains a memory mapped zone, called lowmem, which is contiguously mapped in physical memory, starting from the lowest possible physical address (usually 0). The virtual address where lowmem is mapped is defined by PAGE_OFFSET .

    On a 32bit system, not all available memory can be mapped in lowmem and because of that there is a separate zone in kernel space called highmem which can be used to arbitrarily map physical memory.

    Memory allocated by kmalloc() resides in lowmem and it is physically contiguous. Memory allocated by vmalloc() is not contiguous and does not reside in lowmem (it has a dedicated zone in highmem).

    Structures used for memory mapping¶

    Before discussing the mechanism of memory-mapping a device, we will present some of the basic structures related to the memory management subsystem of the Linux kernel.

    Before discussing about the memory mapping mechanism over a device, we will present some of the basic structures used by the Linux memory management subsystem. Some of the basic structures are: struct page , struct vm_area_struct , struct mm_struct .

    struct page ¶

    struct page is used to embed information about all physical pages in the system. The kernel has a struct page structure for all pages in the system.

    There are many functions that interact with this structure:

    • virt_to_page() returns the page associated with a virtual address
    • pfn_to_page() returns the page associated with a page frame number
    • page_to_pfn() return the page frame number associated with a struct page
    • page_address() returns the virtual address of a struct page ; this functions can be called only for pages from lowmem
    • kmap() creates a mapping in kernel for an arbitrary physical page (can be from highmem) and returns a virtual address that can be used to directly reference the page

    struct vm_area_struct ¶

    struct vm_area_struct holds information about a contiguous virtual memory area. The memory areas of a process can be viewed by inspecting the maps attribute of the process via procfs:

    A memory area is characterized by a start address, a stop address, length, permissions.

    A struct vm_area_struct is created at each mmap() call issued from user space. A driver that supports the mmap() operation must complete and initialize the associated struct vm_area_struct . The most important fields of this structure are:

      vm_start , vm_end — the beginning and the end of the memory area, respectively (these fields also appear in /proc/

    /maps );

  • vm_file — the pointer to the associated file structure (if any);
  • vm_pgoff — the offset of the area within the file;
  • vm_flags — a set of flags;
  • vm_ops — a set of working functions for this area
  • vm_next , vm_prev — the areas of the same process are chained by a list structure

struct mm_struct ¶

struct mm_struct encompasses all memory areas associated with a process. The mm field of struct task_struct is a pointer to the struct mm_struct of the current process.

Device driver memory mapping¶

Memory mapping is one of the most interesting features of a Unix system. From a driver’s point of view, the memory-mapping facility allows direct memory access to a user space device.

To assign a mmap() operation to a driver, the mmap field of the device driver’s struct file_operations must be implemented. If that is the case, the user space process can then use the mmap() system call on a file descriptor associated with the device.

The mmap system call takes the following parameters:

To map memory between a device and user space, the user process must open the device and issue the mmap() system call with the resulting file descriptor.

The device driver mmap() operation has the following signature:

The filp field is a pointer to a struct file created when the device is opened from user space. The vma field is used to indicate the virtual address space where the memory should be mapped by the device. A driver should allocate memory (using kmalloc() , vmalloc() , alloc_pages() ) and then map it to the user address space as indicated by the vma parameter using helper functions such as remap_pfn_range() .

remap_pfn_range() will map a contiguous physical address space into the virtual space represented by vm_area_struct :

remap_pfn_range() expects the following parameters:

  • vma — the virtual memory space in which mapping is made;
  • addr — the virtual address space from where remapping begins; page tables for the virtual address space between addr and addr + size will be formed as needed
  • pfn — the page frame number to which the virtual address should be mapped
  • size — the size (in bytes) of the memory to be mapped
  • prot — protection flags for this mapping

Here is an example of using this function that contiguously maps the physical memory starting at page frame number pfn (memory that was previously allocated) to the vma->vm_start virtual address:

To obtain the page frame number of the physical memory we must consider how the memory allocation was performed. For each kmalloc() , vmalloc() , alloc_pages() , we must used a different approach. For kmalloc() we can use something like:

Источник

Файлы, отображаемые в память

#include
void *mmap( void *addr, size_t len, int prot, int flag, int filedes, off_t off);

Она возвращает адрес начала участка отображаемой памяти или MAP_FAILED в случае неудачи.
Первый аргумент — желаемый адрес начала участка отбраженной памяти. Не знаю, когда это может пригодится. Передаём 0 — тогда ядро само выберет этот адрес.
len — количество байт, которое нужно отобразить в память.
prot — число, определяющее степень защищённости отображенного участка памяти(только чтение, только запись, исполнение, область недоступна). Обычные значения — PROT_READ, PROT_WRITE (можно кобминировать через ИЛИ). Не буду на этом останавливаться — подробнее читайте в манах. Отмечу лишь, что защищённость памяти не установится ниже, чем права, с которыми открыт файл.
flag — описывает атрибуты области. Обычное значение — MAP_SHARED. По поводу остальных — курите маны. Но замечу, что использование MAP_FIXED понижает переносимость приложения, т.к. его подержка является необязательной в POSIX-системах.
filedes — как вы уже догались — дескриптор файла, который нужно отобразить.
off — смещение отображенного участка от начала файла.

Важное замечание. Если вы планируете использовать MMF для записи в файл, перед маппингом необходимо установить конечный размер файла не меньше, чем размер отображенной памяти! Иначе нарвётесь на SIGBUS.

Ниже приведён пример(честно стырен из замечательной книжки «Unix. Профессиональное программирование») программы, которая копирует файл с использованием MMF.

Источник

Читайте также:  Turn windows back in time
Оцените статью