Linux memory mapped file

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.

    Источник

    Linux memory mapped file

    void * mmap(void * start , size_t length , int prot , int flags , int fd , off_t offset );

    int munmap(void * start , size_t length );

    ОПИСАНИЕ

    Аргумент prot описывает желаемый режим защиты памяти (он не должен конфликтовать с режимом открытия файла). Оно является либо PROT_NONE либо побитовым ИЛИ одного или нескольких флагов PROT_*. PROT_EXEC (данные в страницах могут исполняться); PROT_READ (данные можно читать); PROT_WRITE (в эту область можно записывать информацию); PROT_NONE (доступ к этой области памяти запрещен).

    Параметр flags задает тип отражаемого объекта, опции отражения и указывает, принадлежат ли отраженные данные только этому процессу или их могут читать другие. Он состоит из комбинации следующих битов: MAP_FIXED Не использовать другой адрес, если адрес задан в параметрах функции. Если заданный адрес не может быть использован, то функция mmap вернет сообщение об ошибке. Если используется MAP_FIXED, то start должен быть пропорционален размеру страницы. Использование этой опции не рекомендуется. MAP_SHARED Разделить использование этого отражения с другими процессами, отражающими тот же объект. Запись информации в эту область памяти будет эквивалентна записи в файл. Файл может не обновляться до вызова функций msync (2) или munmap (2) . MAP_PRIVATE Создать неразделяемое отражение с механизмом copy-on-write. Запись в эту область памяти не влияет на файл. Не определено, являются или нет изменения в файле после вызова mmap видимыми в отраженном диапазоне.

    Вы должны задать либо MAP_SHARED, либо MAP_PRIVATE.

    Эти три флага описаны в POSIX.1b (бывшем POSIX.4) and SUSv2. В Linux также анализируются следующие нестандартные флаги: MAP_DENYWRITE Этот флаг игнорируется. (Раньше он обозначал, что попытки записи в подчиненные файлы должны завершаться с кодом ошибки ETXTBUSY. Но это стало основой для атак типа ‘отказ-в-доступе’ — ‘denial-of-service’.) MAP_EXECUTABLE Этот флаг игнорируется. MAP_NORESERVE (Используется вместе с MAP_PRIVATE.) Не выделяет страницы пространства подкачки для этого отображения. Если пространство подкачки выделяется, то это частное пространство копирования-при-записи может быть изменено. Если оно не выделено, то можно получить SIGSEGV при записи и отсутствии доступной памяти. MAP_LOCKED (Linux 2.5.37 и выше) Блокировать страницу или размеченную область в памяти так, как это делает mlock() . Этот флаг игнорируется в старых ядрах. MAP_GROWSDOWN Используется для стеков. Для VM системы ядра обозначает, что отображение должно распространяться вниз по памяти. MAP_ANONYMOUS Отображение не резервируется ни в каком файле; аргументы fd и offset игнорируются. Этот флаг вместе с MAP_SHARED реализован с Linux 2.4. MAP_ANON Псевдоним для MAP_ANONYMOUS. Не используется. MAP_FILE Флаг совместимости. Игнорируется.

    MAP_32BIT Поместить размещение в первые 2Гб адресного рпостранства процесса. Игнорируется, если указано MAP_FIXED . Этот флаг сейчас поддерживается только на x86-64 для 64-битных программ.

    Некоторые системы документируют дополнительные флаги MAP_AUTOGROW, MAP_AUTORESRV, MAP_COPY и MAP_LOCAL.

    fd должно быть корректным описателем файла, если только не установлено MAP_ANONYMOUS, так как в этом случае аргумент игнорируется.

    offset должен быть пропорционален размеру страницы, получаемому при помощи функции getpagesize (2).

    Memory mapped by mmap is preserved across fork (2), with the same attributes.

    A file is mapped in multiples of the page size. For a file that is not a multiple of the page size, the remaining memory is zeroed when mapped, and writes to that region are not written out to the file. The effect of changing the size of the underlying file of a mapping on the pages that correspond to added or removed regions of the file is unspecified. Системный вызов munmap удаляет все отражения из заданной области памяти, после чего все ссылки на данную область будут вызывать ошибку «неправильное обращение к памяти» (invalid memory reference). Отражение удаляется автоматически при завершении процесса. С другой стороны, закрытие файла не приведет к снятию отражения.

    Адрес start должно быть кратен размеру страницы. Все страницы, содержащие часть указанного диапазона, не отображены, и последующие ссылки на эти страницы будут генерировать SIGSEGV. Это не будет являться ошибкой, если указанный диапазон не содержит отображенных страниц. Для отображений ‘файл-бэкэнд’ поле st_atime отображаемого файла может быть обновлено в любой момент между mmap() и соответствующим снятием отображения; первое обращение к отображенной странице обновит поле, если оно до этого уже не было обновлено.

    Поля st_ctime и st_mtime файла, отображенного по PROT_WRITE и MAP_SHARED, будут обновлены после записи в отображенний диапазон, и до вызова последующего msync() с флагом MS_SYNC или MS_ASYNC, если такой случится.

    ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ


    ЗАМЕЧАНИЯ


    НАЙДЕННЫЕ ОШИБКИ

    Использование отражаемой области памяти может привести к следующим сигналам: SIGSEGV (попытка записи в область памяти, заданную mmap как область для чтения); SIGBUS (попытка доступа к части буфера, которая не является файлом; например, она может находиться за пределами файла. Подобной является ситуация, когда другой процесс уменьшает длину файла).

    Источник

    Записки программиста

    Отображения файла в память под Linux с помощью mmap

    26 октября 2015

    В прошлый раз мы поговорили об отображении файлов в память при помощи WinAPI, а сегодня разберемся, как то же самое делается под nix-системами, в частности Linux и MacOS. Проверить код под FreeBSD я поленился, но по идее все должно работать и в этой операционной системе. Повторюсь — я почти уверен, что многие читатели сего блога уже знакомы с отображением файлов в память, поэтому пост предназначен для всех остальных читателей.

    Укажем необходимые инклуды и объявим структуру FileMapping, хранящую файловый дескриптор, размер файла и указатель на участок памяти с отображением:

    #include
    #include
    #include
    #include

    struct FileMapping <
    int fd ;
    size_t fsize ;
    unsigned char * dataPtr ;
    > ;

    Рассмотрим чтение из файла с использованием отображения.

    Открываем файл на чтение:

    Узнаем размер файла:

    size_t fsize = ( size_t ) st. st_size ;

    Вызовом mmap создаем отображение файла в память:

    Наконец, заполняем структуру FileMapping и возвращаем указатель на нее в качестве результата:

    FileMapping * mapping = ( FileMapping * ) malloc ( sizeof ( FileMapping ) ) ;
    if ( mapping == nullptr ) <
    std :: cerr «fileMappingCreate — malloc failed, fname = »
    fname std :: endl ;
    munmap ( dataPtr, fsize ) ;
    close ( fd ) ;
    return nullptr ;
    >

    mapping — > fd = fd ;
    mapping — > fsize = fsize ;
    mapping — > dataPtr = dataPtr ;

    Теперь по адресу mapping->dataPtr мы можем читать mapping->fsize байт содержимого файла.

    Как всегда, не забываем освобождать за собой ресурсы, когда они становятся ненужны:

    Вот и все! Сожалею, если вы ожидали чего-то более сложного 🙂 Полную версию исходного кода вы найдете здесь.

    Те, кому представленный материал показался слишком простым, могут в качестве домашнего задания сделать следующее:

    • Взять одну из *BSD систем и проверить, работает ли код на ней;
    • Переписать пример так, чтобы файл можно было не только читать, но и писать в него;
    • Выяснить, можно ли менять размер файла, отображенного в память;
    • Выяснить, что будет, если создать отображение файла, а затем записать в него что-то через обычный вызов write;
    • Погуглить на тему использования mmap в качестве IPC, написать соответствующий пример;

    Признаюсь, сам я эти упражнения не выполнял, так как соответствующая задача мне пока что не подворачивалась. Поэтому буду очень вам благодарен, если вы отпишите о результатах их решения в комментариях.

    Дополнение: Обратите также внимание на системные вызовы mlock / munlock, msync, madvise и mremap. В определенных типах приложений (например, СУБД) они могут быть очень и очень полезны!

    Источник

    Читайте также:  Windows 10 wsl check version
Оцените статью