Когда я должен использовать mmap для доступа к файлам?

276

Среды POSIX предоставляют как минимум два способа доступа к файлам. Там в стандартных системных вызовах open(), read(), write()и друзья, но есть также возможность использования mmap()для отображения файла в виртуальную память.

Когда предпочтительнее использовать один над другим? В чем заключаются их индивидуальные преимущества, включая два интерфейса?

Питер Бернс
источник
16
Смотрите также mmap () против блоков чтения и этот пост Линуса Торвальдса, на который есть ссылка в одном из ответов.
MvG

Ответы:

299

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

mmapтакже позволяет операционной системе оптимизировать операции подкачки. Например, рассмотрим две программы; программа, Aкоторая читает 1MBфайл в буфер, создаваемый с помощью malloc, и программа B, которая mmapsхранит файл размером 1 МБ в памяти. Если операционная система должна выгрузить часть Aпамяти, она должна записать содержимое буфера для замены, прежде чем она сможет повторно использовать память. В этом Bслучае любые немодифицированные mmapd-страницы могут быть немедленно использованы повторно, поскольку ОС знает, как восстановить их из существующего файла, из которого они были mmapизвлечены. (ОС может определить, какие страницы не изменены, изначально пометив доступные для записи mmap«d» страницы как «только для чтения» и обнаружив ошибки сегмента , аналогично стратегии « Копировать при записи» ).

mmapтакже полезно для межпроцессного взаимодействия . Вы можете mmapсделать файл доступным для чтения / записи в процессах, которые должны взаимодействовать, а затем использовать примитивы синхронизации в mmap'dрегионе (для этого и предназначен MAP_HASSEMAPHOREфлаг).

Одно место mmapможет быть неудобным, если вам нужно работать с очень большими файлами на 32-битной машине. Это потому, mmapчто нужно найти непрерывный блок адресов в адресном пространстве вашего процесса, который достаточно велик, чтобы соответствовать всему диапазону отображаемого файла. Это может стать проблемой, если ваше адресное пространство становится фрагментированным, когда у вас может быть 2 ГБ свободного адресного пространства, но ни один из его отдельных диапазонов не может соответствовать отображению файла 1 ГБ. В этом случае вам, возможно, придется отобразить файл более мелкими порциями, чем вы хотели бы, чтобы он подходил.

Другая потенциальная неловкость mmapв качестве замены для чтения / записи заключается в том, что вы должны начать отображение на смещениях размера страницы. Если вы просто хотите получить данные со смещением, Xвам нужно исправить это смещение, чтобы оно было совместимо с mmap.

И , наконец, чтение / запись является единственным способом вы можете работать с некоторыми типами файлов. mmapне может быть использовано на таких вещах, как трубы и тт .

Дон Нойфельд
источник
10
Можете ли вы использовать mmap () для файлов, которые растут? Или размер фиксирован в момент, когда вы выделяете память / файл mmap ()?
Джонатан Леффлер
29
Когда вы делаете вызов mmap, вы должны указать размер. Так что если вы хотите сделать что-то вроде операции с хвостом, это не очень подходит.
Дон Нойфельд
5
Afaik MAP_HASSEMAPHOREявляется специфическим для BSD.
Патрик Шлютер
6
@JonathanLeffler Конечно, вы можете использовать mmap () для файлов, которые растут, но вам придется снова вызывать mmap () с новым размером, когда файл достигает предела пространства, которое вы изначально выделили. PosixMmapFile от LevelDB - хороший пример. Но он перестал использовать mmap из 1.15. Вы можете получить старую версию от Github
baotiao
4
mmap также может пригодиться, если файл необходимо обработать за несколько проходов: стоимость выделения страниц виртуальной памяти оплачивается только один раз.
Джиб
69

Одна из областей, где я обнаружил, что mmap () не является преимуществом, была при чтении небольших файлов (до 16K). Затраты на сбой страницы при чтении всего файла были очень высоки по сравнению с простым системным вызовом read (). Это потому, что ядро ​​иногда может полностью удовлетворить чтение в вашем интервале времени, то есть ваш код не переключается. С ошибкой страницы казалось более вероятным, что будет запланирована другая программа, что приведет к более высокой задержке файловой операции.

Бен Комби
источник
4
+1 Я могу это подтвердить. Для маленьких файлов это быстрее на mallocчасть памяти и делает 1 readв нее. Это позволяет иметь тот же код, который обрабатывает карты памяти, обрабатывает malloc'ed.
Патрик Шлютер
35
Это сказало, ваше оправдание для этого не правильно. Планировщик не имеет никакого отношения к разнице. Разница заключается в доступе на запись к таблицам страниц, который представляет собой глобальную структуру ядра, содержащую информацию о том, какие процессы содержат какую страницу памяти и ее права доступа. Эта операция может быть очень дорогостоящей (это может привести к потере действия в строках кэша, может отменить TLB, таблица является глобальной, поэтому ее необходимо защитить от одновременного доступа и т. Д.). Вам нужен определенный размер карты, чтобы издержки readдоступа были выше, чем затраты на манипулирование виртуальной памятью.
Патрик Шлютер
1
@ PatrickSchlüter Хорошо, я понимаю, что в начале mmap () есть издержки, связанные с изменением таблицы страниц. Скажем, мы отображаем 16K файла в память. Для страницы размером 4К mmapнеобходимо обновить 4 записи в таблице страниц. Но использование readдля копирования в буфер 16K также включает в себя обновление записей таблицы из 4 страниц, не говоря уже о том, что необходимо скопировать 16K в пространство адресов пользователя. Не могли бы вы рассказать о различиях в операциях с таблицей страниц и о том, как это обходится дороже mmap?
flow2k
45

mmapимеет преимущество, когда у вас есть произвольный доступ к большим файлам. Еще одним преимуществом является то, что вы обращаетесь к нему с помощью операций с памятью (memcpy, арифметика указателей), не беспокоясь о буферизации. Нормальный ввод-вывод может иногда быть довольно сложным при использовании буферов, когда у вас есть структуры больше, чем ваш буфер. Код для обработки, который часто трудно понять правильно, mmap, как правило, проще. Тем не менее, есть определенные ловушки при работе с mmap. Как уже упоминалось, mmapнастройка довольно дорогая, поэтому ее стоит использовать только для определенного размера (в зависимости от машины).

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

Вы должны быть осторожны с ограничениями выравнивания вашей архитектуры (SPARC, Itanium), при чтении / записи IO буферы часто правильно выровнены и не перехватываются при разыменовании приведенного указателя.

Вы также должны быть осторожны, чтобы не получить доступ за пределами карты. Это может легко произойти, если вы используете строковые функции на вашей карте, и ваш файл не содержит \ 0 в конце. Он будет работать большую часть времени, когда размер вашего файла не кратен размеру страницы, так как последняя страница заполнена 0 (отображаемая область всегда имеет размер, кратный размеру вашей страницы).

Патрик Шлютер
источник
30

В дополнение к другим приятным ответам, цитата из системного программирования Linux, написанная экспертом Google Робертом Лавом:

Преимущества mmap( )

Управление файлами через mmap( )имеет несколько преимуществ по сравнению со стандартными read( )и write( )системными вызовами. Среди них:

  • Чтение и запись в отображенный в память файл позволяет избежать постороннего копирования, которое происходит при использовании системных вызовов read( )или write( ), когда данные должны копироваться в буфер пространства пользователя и из него.

  • Помимо любых возможных сбоев страниц, чтение и запись в отображенный в память файл не влечет за собой никаких системных вызовов или переключений контекста. Это так же просто, как доступ к памяти.

  • Когда несколько процессов отображают один и тот же объект в память, данные распределяются между всеми процессами. Отображения только для чтения и общие записи с возможностью записи доступны полностью; частные сопоставимые записи доступны для совместного использования со страницами еще не COW (копирование при записи).

  • Поиск вокруг отображения включает в себя тривиальные манипуляции с указателями. Нет необходимости в lseek( )системном вызове.

По этим причинам, mmap( )это разумный выбор для многих приложений.

Недостатки mmap( )

Есть несколько моментов, которые следует учитывать при использовании mmap( ):

  • Отображения памяти всегда представляют собой целое число страниц по размеру. Таким образом, разница между размером файла поддержки и целым числом страниц «теряется» в качестве свободного места. Для небольших файлов значительный процент сопоставления может быть потрачен впустую. Например, при страницах размером 4 КБ 7-байтовое отображение тратит 4089 байт.

  • Отображения памяти должны вписываться в адресное пространство процесса. При использовании 32-разрядного адресного пространства очень большое количество отображений различных размеров может привести к фрагментации адресного пространства, что затрудняет поиск больших свободных смежных областей. Эта проблема, конечно, гораздо менее очевидна с 64-битным адресным пространством.

  • Существуют издержки при создании и поддержании отображений памяти и связанных структур данных внутри ядра. Эти издержки, как правило, устраняются путем устранения двойной копии, упомянутой в предыдущем разделе, особенно для больших и часто используемых файлов.

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

Мильен Микич
источник
13

Отображение памяти имеет огромное преимущество в скорости по сравнению с традиционным вводом-выводом. Это позволяет операционной системе считывать данные из исходного файла при касании страниц в отображенном файле памяти. Это работает путем создания ошибочных страниц, которые ОС обнаруживает, а затем ОС автоматически загружает соответствующие данные из файла.

Это работает так же, как механизм подкачки, и обычно оптимизируется для высокоскоростного ввода-вывода, считывая данные по границам и размерам системных страниц (обычно 4 КБ) - размер, для которого оптимизируется большинство кешей файловой системы.

AndyG
источник
15
Обратите внимание, что mmap () не всегда быстрее read (). Для последовательных чтений mmap () не даст вам ощутимых преимуществ - это основано на эмпирических и теоретических данных. Если вы мне не верите, напишите свой собственный тест.
Тим Купер
1
Я могу привести числа из нашего проекта, своего рода текстовый индекс для базы данных фраз. Индекс имеет размер в несколько гигабайт, а ключи хранятся в троичном дереве. Индекс все еще растет параллельно с доступом для чтения, доступ за пределы отображенных частей осуществляется через pread. В Solaris 9 Sparc (V890) доступ к pread в 2–3 раза медленнее, чем memcpyиз mmap. Но вы правы, что последовательный доступ не обязательно быстрее.
Патрик Шлютер
19
Просто маленькая придирка. Он не работает как механизм пейджинга, это механизм пейджинга. Сопоставление файла - это присвоение области памяти файлу вместо анонимного файла подкачки.
Патрик Шлютер
2

Преимущество, которого пока нет в списке, это возможность mmap()сохранять сопоставление только для чтения как чистые страницы. Если кто-то выделяет буфер в адресном пространстве процесса, а затем использует его read()для заполнения буфера из файла, страницы памяти, соответствующие этому буферу, теперь грязные, так как они были записаны.

Грязные страницы не могут быть сброшены ядром из оперативной памяти. Если есть место подкачки, то они могут быть выгружены для обмена. Но это дорого, и в некоторых системах, таких как небольшие встроенные устройства с только флэш-памятью, подкачки вообще нет. В этом случае буфер будет зависать в ОЗУ до тех пор, пока процесс не завершится или, возможно, не вернет его обратно madvise().

Не написанные на mmap()страницах чистые. Если ядру требуется ОЗУ, оно может просто отбросить их и использовать ОЗУ, в котором находились страницы. Если процесс, который имел сопоставление, снова обращается к нему, это вызывает сбой страницы, и ядро ​​перезагружает страницы из файла, из которого они исходно пришли. , Так же, как они были заселены в первую очередь.

Для этого не требуется более одного процесса, использующего сопоставленный файл, чтобы иметь преимущество.

TrentP
источник
Разве ядро ​​не может удалить «грязную» страницу mmap, предварительно записав ее содержимое в основной файл?
Джереми Фризнер
2
При использовании read()страницы, на которые в конечном итоге помещаются данные, не имеют отношения к файлу, с которого они могли прийти. Поэтому их нельзя выписать, кроме как поменять место. Если файл есть mmap()ed, и сопоставление доступно для записи (в отличие от только для чтения) и записано, то это зависит от того, было ли сопоставление MAP_SHAREDили MAP_PRIVATE. Совместное отображение может / должно быть записано в файл, но частное не может быть.
Трент