Когда я должен использовать mmap для доступа к файлам?
Среды POSIX предоставляют как минимум два способа доступа к файлам. Там стандартные системные вызовы open()
, read()
, write()
и друзей, но есть также возможность использования mmap()
отобразить файл в виртуальную память.
Когда предпочтительнее использовать один над другим? В чем заключаются их индивидуальные преимущества, включая два интерфейса?
5 ответов
mmap отлично подходит, если у вас есть несколько процессов, обращающихся к данным только для чтения из одного и того же файла, что часто встречается в тех серверных системах, которые я пишу. mmap позволяет всем этим процессам совместно использовать одни и те же страницы физической памяти, экономя много памяти.
mmap также позволяет операционной системе оптимизировать операции подкачки. Например, рассмотрим две программы; программа A, которая считывает файл размером 1 МБ в буфер, создаваемый с помощью malloc, и программа B, которая отображает файл размером 1 МБ в память. Если операционная система должна выгрузить часть памяти A, она должна записать содержимое буфера для замены, прежде чем она сможет повторно использовать память. В случае B любые неизмененные страницы mmap'd могут быть немедленно использованы повторно, потому что ОС знает, как восстановить их из существующего файла, из которого они были mmap'd. (ОС может определить, какие страницы не изменены, изначально пометив доступные для записи страницы mmap как доступные только для чтения и обнаружив ошибки сегмента, аналогично стратегии "Копировать при записи").
Mmap также полезен для межпроцессного взаимодействия. Вы можете отображать файл в режиме чтения / записи в процессах, которые должны взаимодействовать, а затем использовать примитивы синхронизации в области mmap (для этого и используется флаг MAP_HASSEMAPHORE).
Mmap может быть неудобным, если вам нужно работать с очень большими файлами на 32-битной машине. Это связано с тем, что mmap должен находить непрерывный блок адресов в адресном пространстве вашего процесса, который достаточно велик, чтобы соответствовать всему диапазону отображаемого файла. Это может стать проблемой, если ваше адресное пространство становится фрагментированным, где у вас может быть 2 ГБ свободного адресного пространства, но ни один из его отдельных диапазонов не может соответствовать отображению файла 1 ГБ. В этом случае вам может понадобиться отобразить файл на более мелкие куски, чем вы хотите, чтобы он соответствовал.
Другая потенциальная неловкость с mmap в качестве замены для чтения / записи заключается в том, что вы должны начать отображение на смещениях размера страницы. Если вы просто хотите получить данные со смещением X, вам нужно исправить это смещение, чтобы оно было совместимо с mmap.
И, наконец, чтение / запись - это единственный способ работы с некоторыми типами файлов. mmap нельзя использовать для таких вещей, как pipe и ttys.
Одна из областей, в которой я обнаружил, что mmap() не является преимуществом, - это чтение небольших файлов (до 16K). Затраты на сбой страницы при чтении всего файла были очень высоки по сравнению с простым системным вызовом read(). Это потому, что ядро иногда может полностью удовлетворить чтение в вашем интервале времени, то есть ваш код не переключается. С ошибкой страницы казалось более вероятным, что будет запланирована другая программа, в результате чего файловая операция будет иметь большую задержку.
mmap
имеет преимущество, когда у вас есть произвольный доступ к большим файлам. Еще одним преимуществом является то, что вы обращаетесь к нему с помощью операций с памятью (memcpy, арифметика указателей), не беспокоясь о буферизации. Нормальный ввод-вывод может иногда быть довольно сложным при использовании буферов, когда у вас есть структуры больше, чем ваш буфер. Код для обработки, который часто трудно понять правильно, mmap, как правило, проще. Тем не менее, есть определенные ловушки при работе с mmap
, Как уже упоминали люди, mmap
установка довольно дорогая, поэтому ее стоит использовать только для данного размера (в зависимости от машины).
Для чистого последовательного доступа к файлу, это также не всегда лучшее решение, хотя соответствующий вызов madvise
может смягчить проблему.
Вы должны быть осторожны с ограничениями выравнивания вашей архитектуры (SPARC, itanium), при чтении / записи IO буферы часто правильно выровнены и не перехватываются при разыменовании приведенного указателя.
Вы также должны быть осторожны, чтобы не получить доступ за пределами карты. Это может легко произойти, если вы используете строковые функции на вашей карте, и ваш файл не содержит \0 в конце. Он будет работать большую часть времени, когда размер вашего файла не кратен размеру страницы, поскольку последняя страница заполнена 0 (отображаемая область всегда имеет размер, кратный размеру вашей страницы).
В дополнение к другим приятным ответам, цитата из системного программирования Linux, написанная экспертом Google Робертом Лавом:
Преимущества
mmap( )
Управление файлами через
mmap( )
имеет несколько преимуществ по сравнению со стандартомread( )
а такжеwrite( )
системные вызовы. Среди них:
Чтение и запись в файл с отображением в памяти позволяет избежать постороннего копирования, которое происходит при использовании
read( )
или жеwrite( )
системные вызовы, где данные должны быть скопированы в буфер пространства пользователя и из него.Помимо любых возможных сбоев страниц, чтение и запись в отображенный в память файл не влечет за собой никаких системных вызовов или переключений контекста. Это так же просто, как доступ к памяти.
Когда несколько процессов отображают один и тот же объект в память, данные распределяются между всеми процессами. Отображения только для чтения и общие записи с возможностью записи доступны полностью; частные сопоставимые записи доступны для совместного использования со страницами еще не COW (копирование при записи).
Поиск вокруг отображения включает в себя тривиальные манипуляции с указателями. Там нет необходимости для
lseek( )
системный вызов.Поэтому,
mmap( )
это разумный выбор для многих приложений.Недостатки
mmap( )
Есть несколько моментов, которые следует учитывать при использовании
mmap( )
:
Отображения памяти всегда представляют собой целое число страниц по размеру. Таким образом, разница между размером файла поддержки и целым числом страниц "теряется" в качестве свободного места. Для небольших файлов значительный процент сопоставления может быть потрачен впустую. Например, при страницах размером 4 КБ 7-байтовое отображение тратит 4089 байт.
Отображения памяти должны вписываться в адресное пространство процесса. При использовании 32-разрядного адресного пространства очень большое количество отображений различных размеров может привести к фрагментации адресного пространства, что затрудняет поиск больших свободных смежных областей. Эта проблема, конечно, гораздо менее очевидна с 64-битным адресным пространством.
Существуют дополнительные затраты на создание и поддержание отображений памяти и связанных структур данных внутри ядра. Эти издержки, как правило, устраняются путем устранения двойной копии, упомянутой в предыдущем разделе, особенно для больших и часто используемых файлов.
По этим причинам преимущества
mmap( )
в наибольшей степени реализуются, когда отображаемый файл имеет большой размер (и, следовательно, любое потерянное пространство составляет небольшой процент от общего сопоставления), или когда общий размер сопоставленного файла равномерно делится на размер страницы (и, таким образом, неиспользуемое пространство не используется.).
Отображение памяти имеет огромное преимущество в скорости по сравнению с традиционным вводом-выводом. Это позволяет операционной системе считывать данные из исходного файла при касании страниц в отображаемом файле памяти. Это работает путем создания ошибочных страниц, которые ОС обнаруживает, а затем ОС автоматически загружает соответствующие данные из файла.
Это работает так же, как механизм подкачки, и обычно оптимизируется для высокоскоростного ввода-вывода, считывая данные по границам и размерам системных страниц (обычно 4 КБ) - размер, для которого оптимизируется большинство кешей файловой системы.
Еще не перечисленное преимущество - это возможность mmap()
сохранить отображение только для чтения как чистые страницы. Если в адресном пространстве процесса выделяется буфер, то используетсяread()
чтобы заполнить буфер из файла, страницы памяти, соответствующие этому буферу, теперь загрязнены, поскольку в них была произведена запись.
Грязные страницы не могут быть удалены ядром из ОЗУ. Если есть место для подкачки, то их можно выгружать для подкачки. Но это дорого, и в некоторых системах, например в небольших встраиваемых устройствах только с флеш-памятью, своп вообще отсутствует. В этом случае буфер будет застрять в ОЗУ до тех пор, пока процесс не завершится, или, возможно, вернет его с помощьюmadvise()
.
Не написано mmap()
страницы чистые. Если ядру требуется ОЗУ, оно может просто отбросить их и использовать ОЗУ, в котором находились страницы. Если процесс, у которого было сопоставление, снова обращается к нему, это вызывает сбой страницы, ядро повторно загружает страницы из файла, из которого они были изначально.. Таким же образом они и были заселены в первую очередь.
Для этого не требуется, чтобы сопоставленный файл использовался более чем одним процессом.